From a53edaa34224fdc2e52a6e4070dd8a7a6c86daca Mon Sep 17 00:00:00 2001 From: Justin Schroeder Date: Wed, 6 Nov 2019 17:17:19 -0500 Subject: [PATCH] Initial tests written for validation parsing and rules --- .vscode/settings.json | 5 +- dist/formulate.esm.js | 280 +++++++++++++++++++++-------- dist/formulate.min.js | 280 +++++++++++++++++++++-------- dist/formulate.umd.js | 280 +++++++++++++++++++++-------- dist/snow.css | 10 ++ dist/snow.min.css | 4 +- src/Formulate.js | 2 + src/FormulateInput.vue | 23 +++ src/FormulateInputErrors.vue | 26 +++ src/libs/context.js | 31 +++- src/libs/rules.js | 41 +++++ src/libs/utils.js | 65 +++++++ test/Formulate.test.js | 1 + test/FormulateInputText.test.js | 307 ++++++++++++++++++-------------- test/rules.test.js | 77 ++++++++ test/utils.test.js | 44 +++++ themes/snow/_inputs.scss | 14 ++ themes/snow/_variables.scss | 2 + 18 files changed, 1115 insertions(+), 377 deletions(-) create mode 100644 src/FormulateInputErrors.vue create mode 100644 src/libs/rules.js create mode 100644 test/rules.test.js create mode 100644 test/utils.test.js diff --git a/.vscode/settings.json b/.vscode/settings.json index a91efeb..f9254df 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,6 @@ { - "jest.showCoverageOnLoad": true + "jest.showCoverageOnLoad": true, + "cSpell.words": [ + "arrayify" + ] } diff --git a/dist/formulate.esm.js b/dist/formulate.esm.js index 8c69fc0..1383921 100644 --- a/dist/formulate.esm.js +++ b/dist/formulate.esm.js @@ -140,6 +140,26 @@ function shallowEqualObjects (objA, objB) { return true } +/** + * Given a string, object, falsey, or array - return an array. + * @param {mixed} item + */ +function arrayify (item) { + if (!item) { + return [] + } + if (typeof item === 'string') { + return [item] + } + if (Array.isArray(item)) { + return item + } + if (typeof item === 'object') { + return Object.values(item) + } + return [] +} + /** * For a single instance of an input, export all of the context needed to fully * render that element. @@ -165,7 +185,8 @@ var context = { typeContext: typeContext, elementAttributes: elementAttributes, logicalLabelPosition: logicalLabelPosition, - isVmodeled: isVmodeled + isVmodeled: isVmodeled, + mergedErrors: mergedErrors }; /** @@ -268,6 +289,16 @@ function createOptionList (options) { return options } +/** + * The merged errors computed property. + */ +function mergedErrors () { + return arrayify(this.errors) + .concat(arrayify(this.error)) + .concat(arrayify(this.validationErrors)) + .reduce(function (errors, err) { return !errors.includes(err) ? errors.concat(err) : errors; }, []) +} + /** * Defines the model used throughout the existing context. * @param {object} context @@ -361,6 +392,14 @@ var script = { debug: { type: Boolean, default: false + }, + errors: { + type: [String, Array, Boolean], + default: false + }, + error: { + type: [String, Boolean], + default: false } }, data: function data () { @@ -560,8 +599,11 @@ var __vue_render__ = function() { staticClass: "formulate-input-help", domProps: { textContent: _vm._s(_vm.help) } }) - : _vm._e() - ] + : _vm._e(), + _vm._v(" "), + _c("FormulateInputErrors", { attrs: { errors: _vm.mergedErrors } }) + ], + 1 ) }; var __vue_staticRenderFns__ = []; @@ -742,6 +784,87 @@ __vue_render__$1._withStripped = true; undefined ); +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// + +var script$2 = { + props: { + errors: { + type: [Boolean, Array], + required: true + } + } +}; + +/* script */ +var __vue_script__$2 = script$2; + +/* template */ +var __vue_render__$2 = function() { + var _vm = this; + var _h = _vm.$createElement; + var _c = _vm._self._c || _h; + return _vm.errors.length + ? _c( + "ul", + { staticClass: "formulate-input-errors" }, + _vm._l(_vm.errors, function(error) { + return _c("li", { + key: error, + staticClass: "formulate-input-error", + domProps: { innerHTML: _vm._s(error) } + }) + }), + 0 + ) + : _vm._e() +}; +var __vue_staticRenderFns__$2 = []; +__vue_render__$2._withStripped = true; + + /* style */ + var __vue_inject_styles__$2 = undefined; + /* scoped */ + var __vue_scope_id__$2 = undefined; + /* module identifier */ + var __vue_module_identifier__$2 = undefined; + /* functional template */ + var __vue_is_functional_template__$2 = false; + /* style inject */ + + /* style inject SSR */ + + /* style inject shadow dom */ + + + + var FormulateInputErrors = normalizeComponent( + { render: __vue_render__$2, staticRenderFns: __vue_staticRenderFns__$2 }, + __vue_inject_styles__$2, + __vue_script__$2, + __vue_scope_id__$2, + __vue_is_functional_template__$2, + __vue_module_identifier__$2, + false, + undefined, + undefined, + undefined + ); + // // // @@ -757,7 +880,7 @@ __vue_render__$1._withStripped = true; // function objectWithoutProperties (obj, exclude) { var target = {}; for (var k in obj) if (Object.prototype.hasOwnProperty.call(obj, k) && exclude.indexOf(k) === -1) target[k] = obj[k]; return target; } -var script$2 = { +var script$3 = { name: 'FormulateInputGroup', props: { context: { @@ -795,10 +918,10 @@ var script$2 = { }; /* script */ -var __vue_script__$2 = script$2; +var __vue_script__$3 = script$3; /* template */ -var __vue_render__$2 = function() { +var __vue_render__$3 = function() { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; @@ -830,17 +953,17 @@ var __vue_render__$2 = function() { 1 ) }; -var __vue_staticRenderFns__$2 = []; -__vue_render__$2._withStripped = true; +var __vue_staticRenderFns__$3 = []; +__vue_render__$3._withStripped = true; /* style */ - var __vue_inject_styles__$2 = undefined; + var __vue_inject_styles__$3 = undefined; /* scoped */ - var __vue_scope_id__$2 = undefined; + var __vue_scope_id__$3 = undefined; /* module identifier */ - var __vue_module_identifier__$2 = undefined; + var __vue_module_identifier__$3 = undefined; /* functional template */ - var __vue_is_functional_template__$2 = false; + var __vue_is_functional_template__$3 = false; /* style inject */ /* style inject SSR */ @@ -850,12 +973,12 @@ __vue_render__$2._withStripped = true; var FormulateInputGroup = normalizeComponent( - { render: __vue_render__$2, staticRenderFns: __vue_staticRenderFns__$2 }, - __vue_inject_styles__$2, - __vue_script__$2, - __vue_scope_id__$2, - __vue_is_functional_template__$2, - __vue_module_identifier__$2, + { render: __vue_render__$3, staticRenderFns: __vue_staticRenderFns__$3 }, + __vue_inject_styles__$3, + __vue_script__$3, + __vue_scope_id__$3, + __vue_is_functional_template__$3, + __vue_module_identifier__$3, false, undefined, undefined, @@ -890,16 +1013,16 @@ var FormulateInputMixin = { // -var script$3 = { +var script$4 = { name: 'FormulateInputBox', mixins: [FormulateInputMixin] }; /* script */ -var __vue_script__$3 = script$3; +var __vue_script__$4 = script$4; /* template */ -var __vue_render__$3 = function() { +var __vue_render__$4 = function() { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; @@ -1028,17 +1151,17 @@ var __vue_render__$3 = function() { ] ) }; -var __vue_staticRenderFns__$3 = []; -__vue_render__$3._withStripped = true; +var __vue_staticRenderFns__$4 = []; +__vue_render__$4._withStripped = true; /* style */ - var __vue_inject_styles__$3 = undefined; + var __vue_inject_styles__$4 = undefined; /* scoped */ - var __vue_scope_id__$3 = undefined; + var __vue_scope_id__$4 = undefined; /* module identifier */ - var __vue_module_identifier__$3 = undefined; + var __vue_module_identifier__$4 = undefined; /* functional template */ - var __vue_is_functional_template__$3 = false; + var __vue_is_functional_template__$4 = false; /* style inject */ /* style inject SSR */ @@ -1048,12 +1171,12 @@ __vue_render__$3._withStripped = true; var FormulateInputBox = normalizeComponent( - { render: __vue_render__$3, staticRenderFns: __vue_staticRenderFns__$3 }, - __vue_inject_styles__$3, - __vue_script__$3, - __vue_scope_id__$3, - __vue_is_functional_template__$3, - __vue_module_identifier__$3, + { render: __vue_render__$4, staticRenderFns: __vue_staticRenderFns__$4 }, + __vue_inject_styles__$4, + __vue_script__$4, + __vue_scope_id__$4, + __vue_is_functional_template__$4, + __vue_module_identifier__$4, false, undefined, undefined, @@ -1062,16 +1185,16 @@ __vue_render__$3._withStripped = true; // -var script$4 = { +var script$5 = { name: 'FormulateInputText', mixins: [FormulateInputMixin] }; /* script */ -var __vue_script__$4 = script$4; +var __vue_script__$5 = script$5; /* template */ -var __vue_render__$4 = function() { +var __vue_render__$5 = function() { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; @@ -1187,17 +1310,17 @@ var __vue_render__$4 = function() { ] ) }; -var __vue_staticRenderFns__$4 = []; -__vue_render__$4._withStripped = true; +var __vue_staticRenderFns__$5 = []; +__vue_render__$5._withStripped = true; /* style */ - var __vue_inject_styles__$4 = undefined; + var __vue_inject_styles__$5 = undefined; /* scoped */ - var __vue_scope_id__$4 = undefined; + var __vue_scope_id__$5 = undefined; /* module identifier */ - var __vue_module_identifier__$4 = undefined; + var __vue_module_identifier__$5 = undefined; /* functional template */ - var __vue_is_functional_template__$4 = false; + var __vue_is_functional_template__$5 = false; /* style inject */ /* style inject SSR */ @@ -1207,12 +1330,12 @@ __vue_render__$4._withStripped = true; var FormulateInputText = normalizeComponent( - { render: __vue_render__$4, staticRenderFns: __vue_staticRenderFns__$4 }, - __vue_inject_styles__$4, - __vue_script__$4, - __vue_scope_id__$4, - __vue_is_functional_template__$4, - __vue_module_identifier__$4, + { render: __vue_render__$5, staticRenderFns: __vue_staticRenderFns__$5 }, + __vue_inject_styles__$5, + __vue_script__$5, + __vue_scope_id__$5, + __vue_is_functional_template__$5, + __vue_module_identifier__$5, false, undefined, undefined, @@ -1221,7 +1344,7 @@ __vue_render__$4._withStripped = true; // -var script$5 = { +var script$6 = { name: 'FormulateInputSelect', mixins: [FormulateInputMixin], computed: { @@ -1238,10 +1361,10 @@ var script$5 = { }; /* script */ -var __vue_script__$5 = script$5; +var __vue_script__$6 = script$6; /* template */ -var __vue_render__$5 = function() { +var __vue_render__$6 = function() { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; @@ -1348,17 +1471,17 @@ var __vue_render__$5 = function() { ] ) }; -var __vue_staticRenderFns__$5 = []; -__vue_render__$5._withStripped = true; +var __vue_staticRenderFns__$6 = []; +__vue_render__$6._withStripped = true; /* style */ - var __vue_inject_styles__$5 = undefined; + var __vue_inject_styles__$6 = undefined; /* scoped */ - var __vue_scope_id__$5 = undefined; + var __vue_scope_id__$6 = undefined; /* module identifier */ - var __vue_module_identifier__$5 = undefined; + var __vue_module_identifier__$6 = undefined; /* functional template */ - var __vue_is_functional_template__$5 = false; + var __vue_is_functional_template__$6 = false; /* style inject */ /* style inject SSR */ @@ -1368,12 +1491,12 @@ __vue_render__$5._withStripped = true; var FormulateInputSelect = normalizeComponent( - { render: __vue_render__$5, staticRenderFns: __vue_staticRenderFns__$5 }, - __vue_inject_styles__$5, - __vue_script__$5, - __vue_scope_id__$5, - __vue_is_functional_template__$5, - __vue_module_identifier__$5, + { render: __vue_render__$6, staticRenderFns: __vue_staticRenderFns__$6 }, + __vue_inject_styles__$6, + __vue_script__$6, + __vue_scope_id__$6, + __vue_is_functional_template__$6, + __vue_module_identifier__$6, false, undefined, undefined, @@ -1382,16 +1505,16 @@ __vue_render__$5._withStripped = true; // -var script$6 = { +var script$7 = { name: 'FormulateInputTextArea', mixins: [FormulateInputMixin] }; /* script */ -var __vue_script__$6 = script$6; +var __vue_script__$7 = script$7; /* template */ -var __vue_render__$6 = function() { +var __vue_render__$7 = function() { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; @@ -1431,17 +1554,17 @@ var __vue_render__$6 = function() { ] ) }; -var __vue_staticRenderFns__$6 = []; -__vue_render__$6._withStripped = true; +var __vue_staticRenderFns__$7 = []; +__vue_render__$7._withStripped = true; /* style */ - var __vue_inject_styles__$6 = undefined; + var __vue_inject_styles__$7 = undefined; /* scoped */ - var __vue_scope_id__$6 = undefined; + var __vue_scope_id__$7 = undefined; /* module identifier */ - var __vue_module_identifier__$6 = undefined; + var __vue_module_identifier__$7 = undefined; /* functional template */ - var __vue_is_functional_template__$6 = false; + var __vue_is_functional_template__$7 = false; /* style inject */ /* style inject SSR */ @@ -1451,12 +1574,12 @@ __vue_render__$6._withStripped = true; var FormulateInputTextArea = normalizeComponent( - { render: __vue_render__$6, staticRenderFns: __vue_staticRenderFns__$6 }, - __vue_inject_styles__$6, - __vue_script__$6, - __vue_scope_id__$6, - __vue_is_functional_template__$6, - __vue_module_identifier__$6, + { render: __vue_render__$7, staticRenderFns: __vue_staticRenderFns__$7 }, + __vue_inject_styles__$7, + __vue_script__$7, + __vue_scope_id__$7, + __vue_is_functional_template__$7, + __vue_module_identifier__$7, false, undefined, undefined, @@ -1471,6 +1594,7 @@ var Formulate = function Formulate () { components: { FormulateForm: FormulateForm, FormulateInput: FormulateInput, + FormulateInputErrors: FormulateInputErrors, FormulateInputBox: FormulateInputBox, FormulateInputText: FormulateInputText, FormulateInputGroup: FormulateInputGroup, diff --git a/dist/formulate.min.js b/dist/formulate.min.js index 1ac8959..b3fa2e1 100644 --- a/dist/formulate.min.js +++ b/dist/formulate.min.js @@ -143,6 +143,26 @@ var Formulate = (function (exports, isPlainObject, nanoid) { return true } + /** + * Given a string, object, falsey, or array - return an array. + * @param {mixed} item + */ + function arrayify (item) { + if (!item) { + return [] + } + if (typeof item === 'string') { + return [item] + } + if (Array.isArray(item)) { + return item + } + if (typeof item === 'object') { + return Object.values(item) + } + return [] + } + /** * For a single instance of an input, export all of the context needed to fully * render that element. @@ -168,7 +188,8 @@ var Formulate = (function (exports, isPlainObject, nanoid) { typeContext: typeContext, elementAttributes: elementAttributes, logicalLabelPosition: logicalLabelPosition, - isVmodeled: isVmodeled + isVmodeled: isVmodeled, + mergedErrors: mergedErrors }; /** @@ -271,6 +292,16 @@ var Formulate = (function (exports, isPlainObject, nanoid) { return options } + /** + * The merged errors computed property. + */ + function mergedErrors () { + return arrayify(this.errors) + .concat(arrayify(this.error)) + .concat(arrayify(this.validationErrors)) + .reduce(function (errors, err) { return !errors.includes(err) ? errors.concat(err) : errors; }, []) + } + /** * Defines the model used throughout the existing context. * @param {object} context @@ -364,6 +395,14 @@ var Formulate = (function (exports, isPlainObject, nanoid) { debug: { type: Boolean, default: false + }, + errors: { + type: [String, Array, Boolean], + default: false + }, + error: { + type: [String, Boolean], + default: false } }, data: function data () { @@ -563,8 +602,11 @@ var Formulate = (function (exports, isPlainObject, nanoid) { staticClass: "formulate-input-help", domProps: { textContent: _vm._s(_vm.help) } }) - : _vm._e() - ] + : _vm._e(), + _vm._v(" "), + _c("FormulateInputErrors", { attrs: { errors: _vm.mergedErrors } }) + ], + 1 ) }; var __vue_staticRenderFns__ = []; @@ -745,6 +787,87 @@ var Formulate = (function (exports, isPlainObject, nanoid) { undefined ); + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + + var script$2 = { + props: { + errors: { + type: [Boolean, Array], + required: true + } + } + }; + + /* script */ + var __vue_script__$2 = script$2; + + /* template */ + var __vue_render__$2 = function() { + var _vm = this; + var _h = _vm.$createElement; + var _c = _vm._self._c || _h; + return _vm.errors.length + ? _c( + "ul", + { staticClass: "formulate-input-errors" }, + _vm._l(_vm.errors, function(error) { + return _c("li", { + key: error, + staticClass: "formulate-input-error", + domProps: { innerHTML: _vm._s(error) } + }) + }), + 0 + ) + : _vm._e() + }; + var __vue_staticRenderFns__$2 = []; + __vue_render__$2._withStripped = true; + + /* style */ + var __vue_inject_styles__$2 = undefined; + /* scoped */ + var __vue_scope_id__$2 = undefined; + /* module identifier */ + var __vue_module_identifier__$2 = undefined; + /* functional template */ + var __vue_is_functional_template__$2 = false; + /* style inject */ + + /* style inject SSR */ + + /* style inject shadow dom */ + + + + var FormulateInputErrors = normalizeComponent( + { render: __vue_render__$2, staticRenderFns: __vue_staticRenderFns__$2 }, + __vue_inject_styles__$2, + __vue_script__$2, + __vue_scope_id__$2, + __vue_is_functional_template__$2, + __vue_module_identifier__$2, + false, + undefined, + undefined, + undefined + ); + // // // @@ -760,7 +883,7 @@ var Formulate = (function (exports, isPlainObject, nanoid) { // function objectWithoutProperties (obj, exclude) { var target = {}; for (var k in obj) if (Object.prototype.hasOwnProperty.call(obj, k) && exclude.indexOf(k) === -1) target[k] = obj[k]; return target; } - var script$2 = { + var script$3 = { name: 'FormulateInputGroup', props: { context: { @@ -798,10 +921,10 @@ var Formulate = (function (exports, isPlainObject, nanoid) { }; /* script */ - var __vue_script__$2 = script$2; + var __vue_script__$3 = script$3; /* template */ - var __vue_render__$2 = function() { + var __vue_render__$3 = function() { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; @@ -833,17 +956,17 @@ var Formulate = (function (exports, isPlainObject, nanoid) { 1 ) }; - var __vue_staticRenderFns__$2 = []; - __vue_render__$2._withStripped = true; + var __vue_staticRenderFns__$3 = []; + __vue_render__$3._withStripped = true; /* style */ - var __vue_inject_styles__$2 = undefined; + var __vue_inject_styles__$3 = undefined; /* scoped */ - var __vue_scope_id__$2 = undefined; + var __vue_scope_id__$3 = undefined; /* module identifier */ - var __vue_module_identifier__$2 = undefined; + var __vue_module_identifier__$3 = undefined; /* functional template */ - var __vue_is_functional_template__$2 = false; + var __vue_is_functional_template__$3 = false; /* style inject */ /* style inject SSR */ @@ -853,12 +976,12 @@ var Formulate = (function (exports, isPlainObject, nanoid) { var FormulateInputGroup = normalizeComponent( - { render: __vue_render__$2, staticRenderFns: __vue_staticRenderFns__$2 }, - __vue_inject_styles__$2, - __vue_script__$2, - __vue_scope_id__$2, - __vue_is_functional_template__$2, - __vue_module_identifier__$2, + { render: __vue_render__$3, staticRenderFns: __vue_staticRenderFns__$3 }, + __vue_inject_styles__$3, + __vue_script__$3, + __vue_scope_id__$3, + __vue_is_functional_template__$3, + __vue_module_identifier__$3, false, undefined, undefined, @@ -893,16 +1016,16 @@ var Formulate = (function (exports, isPlainObject, nanoid) { // - var script$3 = { + var script$4 = { name: 'FormulateInputBox', mixins: [FormulateInputMixin] }; /* script */ - var __vue_script__$3 = script$3; + var __vue_script__$4 = script$4; /* template */ - var __vue_render__$3 = function() { + var __vue_render__$4 = function() { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; @@ -1031,17 +1154,17 @@ var Formulate = (function (exports, isPlainObject, nanoid) { ] ) }; - var __vue_staticRenderFns__$3 = []; - __vue_render__$3._withStripped = true; + var __vue_staticRenderFns__$4 = []; + __vue_render__$4._withStripped = true; /* style */ - var __vue_inject_styles__$3 = undefined; + var __vue_inject_styles__$4 = undefined; /* scoped */ - var __vue_scope_id__$3 = undefined; + var __vue_scope_id__$4 = undefined; /* module identifier */ - var __vue_module_identifier__$3 = undefined; + var __vue_module_identifier__$4 = undefined; /* functional template */ - var __vue_is_functional_template__$3 = false; + var __vue_is_functional_template__$4 = false; /* style inject */ /* style inject SSR */ @@ -1051,12 +1174,12 @@ var Formulate = (function (exports, isPlainObject, nanoid) { var FormulateInputBox = normalizeComponent( - { render: __vue_render__$3, staticRenderFns: __vue_staticRenderFns__$3 }, - __vue_inject_styles__$3, - __vue_script__$3, - __vue_scope_id__$3, - __vue_is_functional_template__$3, - __vue_module_identifier__$3, + { render: __vue_render__$4, staticRenderFns: __vue_staticRenderFns__$4 }, + __vue_inject_styles__$4, + __vue_script__$4, + __vue_scope_id__$4, + __vue_is_functional_template__$4, + __vue_module_identifier__$4, false, undefined, undefined, @@ -1065,16 +1188,16 @@ var Formulate = (function (exports, isPlainObject, nanoid) { // - var script$4 = { + var script$5 = { name: 'FormulateInputText', mixins: [FormulateInputMixin] }; /* script */ - var __vue_script__$4 = script$4; + var __vue_script__$5 = script$5; /* template */ - var __vue_render__$4 = function() { + var __vue_render__$5 = function() { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; @@ -1190,17 +1313,17 @@ var Formulate = (function (exports, isPlainObject, nanoid) { ] ) }; - var __vue_staticRenderFns__$4 = []; - __vue_render__$4._withStripped = true; + var __vue_staticRenderFns__$5 = []; + __vue_render__$5._withStripped = true; /* style */ - var __vue_inject_styles__$4 = undefined; + var __vue_inject_styles__$5 = undefined; /* scoped */ - var __vue_scope_id__$4 = undefined; + var __vue_scope_id__$5 = undefined; /* module identifier */ - var __vue_module_identifier__$4 = undefined; + var __vue_module_identifier__$5 = undefined; /* functional template */ - var __vue_is_functional_template__$4 = false; + var __vue_is_functional_template__$5 = false; /* style inject */ /* style inject SSR */ @@ -1210,12 +1333,12 @@ var Formulate = (function (exports, isPlainObject, nanoid) { var FormulateInputText = normalizeComponent( - { render: __vue_render__$4, staticRenderFns: __vue_staticRenderFns__$4 }, - __vue_inject_styles__$4, - __vue_script__$4, - __vue_scope_id__$4, - __vue_is_functional_template__$4, - __vue_module_identifier__$4, + { render: __vue_render__$5, staticRenderFns: __vue_staticRenderFns__$5 }, + __vue_inject_styles__$5, + __vue_script__$5, + __vue_scope_id__$5, + __vue_is_functional_template__$5, + __vue_module_identifier__$5, false, undefined, undefined, @@ -1224,7 +1347,7 @@ var Formulate = (function (exports, isPlainObject, nanoid) { // - var script$5 = { + var script$6 = { name: 'FormulateInputSelect', mixins: [FormulateInputMixin], computed: { @@ -1241,10 +1364,10 @@ var Formulate = (function (exports, isPlainObject, nanoid) { }; /* script */ - var __vue_script__$5 = script$5; + var __vue_script__$6 = script$6; /* template */ - var __vue_render__$5 = function() { + var __vue_render__$6 = function() { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; @@ -1351,17 +1474,17 @@ var Formulate = (function (exports, isPlainObject, nanoid) { ] ) }; - var __vue_staticRenderFns__$5 = []; - __vue_render__$5._withStripped = true; + var __vue_staticRenderFns__$6 = []; + __vue_render__$6._withStripped = true; /* style */ - var __vue_inject_styles__$5 = undefined; + var __vue_inject_styles__$6 = undefined; /* scoped */ - var __vue_scope_id__$5 = undefined; + var __vue_scope_id__$6 = undefined; /* module identifier */ - var __vue_module_identifier__$5 = undefined; + var __vue_module_identifier__$6 = undefined; /* functional template */ - var __vue_is_functional_template__$5 = false; + var __vue_is_functional_template__$6 = false; /* style inject */ /* style inject SSR */ @@ -1371,12 +1494,12 @@ var Formulate = (function (exports, isPlainObject, nanoid) { var FormulateInputSelect = normalizeComponent( - { render: __vue_render__$5, staticRenderFns: __vue_staticRenderFns__$5 }, - __vue_inject_styles__$5, - __vue_script__$5, - __vue_scope_id__$5, - __vue_is_functional_template__$5, - __vue_module_identifier__$5, + { render: __vue_render__$6, staticRenderFns: __vue_staticRenderFns__$6 }, + __vue_inject_styles__$6, + __vue_script__$6, + __vue_scope_id__$6, + __vue_is_functional_template__$6, + __vue_module_identifier__$6, false, undefined, undefined, @@ -1385,16 +1508,16 @@ var Formulate = (function (exports, isPlainObject, nanoid) { // - var script$6 = { + var script$7 = { name: 'FormulateInputTextArea', mixins: [FormulateInputMixin] }; /* script */ - var __vue_script__$6 = script$6; + var __vue_script__$7 = script$7; /* template */ - var __vue_render__$6 = function() { + var __vue_render__$7 = function() { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; @@ -1434,17 +1557,17 @@ var Formulate = (function (exports, isPlainObject, nanoid) { ] ) }; - var __vue_staticRenderFns__$6 = []; - __vue_render__$6._withStripped = true; + var __vue_staticRenderFns__$7 = []; + __vue_render__$7._withStripped = true; /* style */ - var __vue_inject_styles__$6 = undefined; + var __vue_inject_styles__$7 = undefined; /* scoped */ - var __vue_scope_id__$6 = undefined; + var __vue_scope_id__$7 = undefined; /* module identifier */ - var __vue_module_identifier__$6 = undefined; + var __vue_module_identifier__$7 = undefined; /* functional template */ - var __vue_is_functional_template__$6 = false; + var __vue_is_functional_template__$7 = false; /* style inject */ /* style inject SSR */ @@ -1454,12 +1577,12 @@ var Formulate = (function (exports, isPlainObject, nanoid) { var FormulateInputTextArea = normalizeComponent( - { render: __vue_render__$6, staticRenderFns: __vue_staticRenderFns__$6 }, - __vue_inject_styles__$6, - __vue_script__$6, - __vue_scope_id__$6, - __vue_is_functional_template__$6, - __vue_module_identifier__$6, + { render: __vue_render__$7, staticRenderFns: __vue_staticRenderFns__$7 }, + __vue_inject_styles__$7, + __vue_script__$7, + __vue_scope_id__$7, + __vue_is_functional_template__$7, + __vue_module_identifier__$7, false, undefined, undefined, @@ -1474,6 +1597,7 @@ var Formulate = (function (exports, isPlainObject, nanoid) { components: { FormulateForm: FormulateForm, FormulateInput: FormulateInput, + FormulateInputErrors: FormulateInputErrors, FormulateInputBox: FormulateInputBox, FormulateInputText: FormulateInputText, FormulateInputGroup: FormulateInputGroup, diff --git a/dist/formulate.umd.js b/dist/formulate.umd.js index 216a36d..d47d5ba 100644 --- a/dist/formulate.umd.js +++ b/dist/formulate.umd.js @@ -146,6 +146,26 @@ return true } + /** + * Given a string, object, falsey, or array - return an array. + * @param {mixed} item + */ + function arrayify (item) { + if (!item) { + return [] + } + if (typeof item === 'string') { + return [item] + } + if (Array.isArray(item)) { + return item + } + if (typeof item === 'object') { + return Object.values(item) + } + return [] + } + /** * For a single instance of an input, export all of the context needed to fully * render that element. @@ -171,7 +191,8 @@ typeContext: typeContext, elementAttributes: elementAttributes, logicalLabelPosition: logicalLabelPosition, - isVmodeled: isVmodeled + isVmodeled: isVmodeled, + mergedErrors: mergedErrors }; /** @@ -274,6 +295,16 @@ return options } + /** + * The merged errors computed property. + */ + function mergedErrors () { + return arrayify(this.errors) + .concat(arrayify(this.error)) + .concat(arrayify(this.validationErrors)) + .reduce(function (errors, err) { return !errors.includes(err) ? errors.concat(err) : errors; }, []) + } + /** * Defines the model used throughout the existing context. * @param {object} context @@ -367,6 +398,14 @@ debug: { type: Boolean, default: false + }, + errors: { + type: [String, Array, Boolean], + default: false + }, + error: { + type: [String, Boolean], + default: false } }, data: function data () { @@ -566,8 +605,11 @@ staticClass: "formulate-input-help", domProps: { textContent: _vm._s(_vm.help) } }) - : _vm._e() - ] + : _vm._e(), + _vm._v(" "), + _c("FormulateInputErrors", { attrs: { errors: _vm.mergedErrors } }) + ], + 1 ) }; var __vue_staticRenderFns__ = []; @@ -748,6 +790,87 @@ undefined ); + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + + var script$2 = { + props: { + errors: { + type: [Boolean, Array], + required: true + } + } + }; + + /* script */ + var __vue_script__$2 = script$2; + + /* template */ + var __vue_render__$2 = function() { + var _vm = this; + var _h = _vm.$createElement; + var _c = _vm._self._c || _h; + return _vm.errors.length + ? _c( + "ul", + { staticClass: "formulate-input-errors" }, + _vm._l(_vm.errors, function(error) { + return _c("li", { + key: error, + staticClass: "formulate-input-error", + domProps: { innerHTML: _vm._s(error) } + }) + }), + 0 + ) + : _vm._e() + }; + var __vue_staticRenderFns__$2 = []; + __vue_render__$2._withStripped = true; + + /* style */ + var __vue_inject_styles__$2 = undefined; + /* scoped */ + var __vue_scope_id__$2 = undefined; + /* module identifier */ + var __vue_module_identifier__$2 = undefined; + /* functional template */ + var __vue_is_functional_template__$2 = false; + /* style inject */ + + /* style inject SSR */ + + /* style inject shadow dom */ + + + + var FormulateInputErrors = normalizeComponent( + { render: __vue_render__$2, staticRenderFns: __vue_staticRenderFns__$2 }, + __vue_inject_styles__$2, + __vue_script__$2, + __vue_scope_id__$2, + __vue_is_functional_template__$2, + __vue_module_identifier__$2, + false, + undefined, + undefined, + undefined + ); + // // // @@ -763,7 +886,7 @@ // function objectWithoutProperties (obj, exclude) { var target = {}; for (var k in obj) if (Object.prototype.hasOwnProperty.call(obj, k) && exclude.indexOf(k) === -1) target[k] = obj[k]; return target; } - var script$2 = { + var script$3 = { name: 'FormulateInputGroup', props: { context: { @@ -801,10 +924,10 @@ }; /* script */ - var __vue_script__$2 = script$2; + var __vue_script__$3 = script$3; /* template */ - var __vue_render__$2 = function() { + var __vue_render__$3 = function() { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; @@ -836,17 +959,17 @@ 1 ) }; - var __vue_staticRenderFns__$2 = []; - __vue_render__$2._withStripped = true; + var __vue_staticRenderFns__$3 = []; + __vue_render__$3._withStripped = true; /* style */ - var __vue_inject_styles__$2 = undefined; + var __vue_inject_styles__$3 = undefined; /* scoped */ - var __vue_scope_id__$2 = undefined; + var __vue_scope_id__$3 = undefined; /* module identifier */ - var __vue_module_identifier__$2 = undefined; + var __vue_module_identifier__$3 = undefined; /* functional template */ - var __vue_is_functional_template__$2 = false; + var __vue_is_functional_template__$3 = false; /* style inject */ /* style inject SSR */ @@ -856,12 +979,12 @@ var FormulateInputGroup = normalizeComponent( - { render: __vue_render__$2, staticRenderFns: __vue_staticRenderFns__$2 }, - __vue_inject_styles__$2, - __vue_script__$2, - __vue_scope_id__$2, - __vue_is_functional_template__$2, - __vue_module_identifier__$2, + { render: __vue_render__$3, staticRenderFns: __vue_staticRenderFns__$3 }, + __vue_inject_styles__$3, + __vue_script__$3, + __vue_scope_id__$3, + __vue_is_functional_template__$3, + __vue_module_identifier__$3, false, undefined, undefined, @@ -896,16 +1019,16 @@ // - var script$3 = { + var script$4 = { name: 'FormulateInputBox', mixins: [FormulateInputMixin] }; /* script */ - var __vue_script__$3 = script$3; + var __vue_script__$4 = script$4; /* template */ - var __vue_render__$3 = function() { + var __vue_render__$4 = function() { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; @@ -1034,17 +1157,17 @@ ] ) }; - var __vue_staticRenderFns__$3 = []; - __vue_render__$3._withStripped = true; + var __vue_staticRenderFns__$4 = []; + __vue_render__$4._withStripped = true; /* style */ - var __vue_inject_styles__$3 = undefined; + var __vue_inject_styles__$4 = undefined; /* scoped */ - var __vue_scope_id__$3 = undefined; + var __vue_scope_id__$4 = undefined; /* module identifier */ - var __vue_module_identifier__$3 = undefined; + var __vue_module_identifier__$4 = undefined; /* functional template */ - var __vue_is_functional_template__$3 = false; + var __vue_is_functional_template__$4 = false; /* style inject */ /* style inject SSR */ @@ -1054,12 +1177,12 @@ var FormulateInputBox = normalizeComponent( - { render: __vue_render__$3, staticRenderFns: __vue_staticRenderFns__$3 }, - __vue_inject_styles__$3, - __vue_script__$3, - __vue_scope_id__$3, - __vue_is_functional_template__$3, - __vue_module_identifier__$3, + { render: __vue_render__$4, staticRenderFns: __vue_staticRenderFns__$4 }, + __vue_inject_styles__$4, + __vue_script__$4, + __vue_scope_id__$4, + __vue_is_functional_template__$4, + __vue_module_identifier__$4, false, undefined, undefined, @@ -1068,16 +1191,16 @@ // - var script$4 = { + var script$5 = { name: 'FormulateInputText', mixins: [FormulateInputMixin] }; /* script */ - var __vue_script__$4 = script$4; + var __vue_script__$5 = script$5; /* template */ - var __vue_render__$4 = function() { + var __vue_render__$5 = function() { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; @@ -1193,17 +1316,17 @@ ] ) }; - var __vue_staticRenderFns__$4 = []; - __vue_render__$4._withStripped = true; + var __vue_staticRenderFns__$5 = []; + __vue_render__$5._withStripped = true; /* style */ - var __vue_inject_styles__$4 = undefined; + var __vue_inject_styles__$5 = undefined; /* scoped */ - var __vue_scope_id__$4 = undefined; + var __vue_scope_id__$5 = undefined; /* module identifier */ - var __vue_module_identifier__$4 = undefined; + var __vue_module_identifier__$5 = undefined; /* functional template */ - var __vue_is_functional_template__$4 = false; + var __vue_is_functional_template__$5 = false; /* style inject */ /* style inject SSR */ @@ -1213,12 +1336,12 @@ var FormulateInputText = normalizeComponent( - { render: __vue_render__$4, staticRenderFns: __vue_staticRenderFns__$4 }, - __vue_inject_styles__$4, - __vue_script__$4, - __vue_scope_id__$4, - __vue_is_functional_template__$4, - __vue_module_identifier__$4, + { render: __vue_render__$5, staticRenderFns: __vue_staticRenderFns__$5 }, + __vue_inject_styles__$5, + __vue_script__$5, + __vue_scope_id__$5, + __vue_is_functional_template__$5, + __vue_module_identifier__$5, false, undefined, undefined, @@ -1227,7 +1350,7 @@ // - var script$5 = { + var script$6 = { name: 'FormulateInputSelect', mixins: [FormulateInputMixin], computed: { @@ -1244,10 +1367,10 @@ }; /* script */ - var __vue_script__$5 = script$5; + var __vue_script__$6 = script$6; /* template */ - var __vue_render__$5 = function() { + var __vue_render__$6 = function() { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; @@ -1354,17 +1477,17 @@ ] ) }; - var __vue_staticRenderFns__$5 = []; - __vue_render__$5._withStripped = true; + var __vue_staticRenderFns__$6 = []; + __vue_render__$6._withStripped = true; /* style */ - var __vue_inject_styles__$5 = undefined; + var __vue_inject_styles__$6 = undefined; /* scoped */ - var __vue_scope_id__$5 = undefined; + var __vue_scope_id__$6 = undefined; /* module identifier */ - var __vue_module_identifier__$5 = undefined; + var __vue_module_identifier__$6 = undefined; /* functional template */ - var __vue_is_functional_template__$5 = false; + var __vue_is_functional_template__$6 = false; /* style inject */ /* style inject SSR */ @@ -1374,12 +1497,12 @@ var FormulateInputSelect = normalizeComponent( - { render: __vue_render__$5, staticRenderFns: __vue_staticRenderFns__$5 }, - __vue_inject_styles__$5, - __vue_script__$5, - __vue_scope_id__$5, - __vue_is_functional_template__$5, - __vue_module_identifier__$5, + { render: __vue_render__$6, staticRenderFns: __vue_staticRenderFns__$6 }, + __vue_inject_styles__$6, + __vue_script__$6, + __vue_scope_id__$6, + __vue_is_functional_template__$6, + __vue_module_identifier__$6, false, undefined, undefined, @@ -1388,16 +1511,16 @@ // - var script$6 = { + var script$7 = { name: 'FormulateInputTextArea', mixins: [FormulateInputMixin] }; /* script */ - var __vue_script__$6 = script$6; + var __vue_script__$7 = script$7; /* template */ - var __vue_render__$6 = function() { + var __vue_render__$7 = function() { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; @@ -1437,17 +1560,17 @@ ] ) }; - var __vue_staticRenderFns__$6 = []; - __vue_render__$6._withStripped = true; + var __vue_staticRenderFns__$7 = []; + __vue_render__$7._withStripped = true; /* style */ - var __vue_inject_styles__$6 = undefined; + var __vue_inject_styles__$7 = undefined; /* scoped */ - var __vue_scope_id__$6 = undefined; + var __vue_scope_id__$7 = undefined; /* module identifier */ - var __vue_module_identifier__$6 = undefined; + var __vue_module_identifier__$7 = undefined; /* functional template */ - var __vue_is_functional_template__$6 = false; + var __vue_is_functional_template__$7 = false; /* style inject */ /* style inject SSR */ @@ -1457,12 +1580,12 @@ var FormulateInputTextArea = normalizeComponent( - { render: __vue_render__$6, staticRenderFns: __vue_staticRenderFns__$6 }, - __vue_inject_styles__$6, - __vue_script__$6, - __vue_scope_id__$6, - __vue_is_functional_template__$6, - __vue_module_identifier__$6, + { render: __vue_render__$7, staticRenderFns: __vue_staticRenderFns__$7 }, + __vue_inject_styles__$7, + __vue_script__$7, + __vue_scope_id__$7, + __vue_is_functional_template__$7, + __vue_module_identifier__$7, false, undefined, undefined, @@ -1477,6 +1600,7 @@ components: { FormulateForm: FormulateForm, FormulateInput: FormulateInput, + FormulateInputErrors: FormulateInputErrors, FormulateInputBox: FormulateInputBox, FormulateInputText: FormulateInputText, FormulateInputGroup: FormulateInputGroup, diff --git a/dist/snow.css b/dist/snow.css index ea0d409..dae8954 100644 --- a/dist/snow.css +++ b/dist/snow.css @@ -15,6 +15,16 @@ font-weight: 300; line-height: 1.5; margin-bottom: .25em; } + .formulate-input .formulate-input-errors { + list-style-type: none; + padding: 0; + margin: 0; } + .formulate-input .formulate-input-error { + color: #960505; + font-size: .8em; + font-weight: 300; + line-height: 1.5; + margin-bottom: .25em; } .formulate-input .formulate-input-group-item { margin-bottom: .5em; } .formulate-input:last-child { diff --git a/dist/snow.min.css b/dist/snow.min.css index 6e1daa9..fbb25f0 100644 --- a/dist/snow.min.css +++ b/dist/snow.min.css @@ -1,2 +1,2 @@ -.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{-webkit-appearance:none;-moz-appearance:none;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::-webkit-input-placeholder{color:#a8a8a8}.formulate-input[data-classification=text] input::-moz-placeholder{color:#a8a8a8}.formulate-input[data-classification=text] input:-ms-input-placeholder{color:#a8a8a8}.formulate-input[data-classification=text] input::-ms-input-placeholder{color:#a8a8a8}.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{-webkit-appearance:none;-moz-appearance:none;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::-webkit-input-placeholder{color:#a8a8a8}.formulate-input[data-classification=textarea] textarea::-moz-placeholder{color:#a8a8a8}.formulate-input[data-classification=textarea] textarea:-ms-input-placeholder{color:#a8a8a8}.formulate-input[data-classification=textarea] textarea::-ms-input-placeholder{color:#a8a8a8}.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-color:#cecece transparent transparent;border-style:solid;border-width:.3em .3em 0;top:50%;margin-top:-.1em;right:1em;position:absolute}.formulate-input[data-classification=select] select{-webkit-appearance:none;-moz-appearance:none;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;display:block;width:100%;font-weight:400;padding:.75em 2em .75em .75em}.formulate-input[data-classification=select] select::-webkit-input-placeholder{color:#a8a8a8}.formulate-input[data-classification=select] select::-moz-placeholder{color:#a8a8a8}.formulate-input[data-classification=select] select:-ms-input-placeholder{color:#a8a8a8}.formulate-input[data-classification=select] select::-ms-input-placeholder{color:#a8a8a8}.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-element,.formulate-input[data-classification=box] .formulate-input-wrapper{display:-webkit-box;display:flex;-webkit-box-align:center;align-items:center}.formulate-input[data-classification=box] .formulate-input-element{overflow:hidden}.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:100%;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,')}.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} -/*# sourceMappingURL=data:application/json;base64,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QUFxQiIsImZpbGUiOiJzdGRpbiIsInNvdXJjZXNDb250ZW50IjpbIi5mb3JtdWxhdGUtaW5wdXQge1xuICBtYXJnaW4tYm90dG9tOiAyZW07IH1cbiAgLmZvcm11bGF0ZS1pbnB1dCAuZm9ybXVsYXRlLWlucHV0LWxhYmVsIHtcbiAgICBkaXNwbGF5OiBibG9jaztcbiAgICBsaW5lLWhlaWdodDogMS41O1xuICAgIGZvbnQtc2l6ZTogLjllbTtcbiAgICBmb250LXdlaWdodDogNjAwO1xuICAgIG1hcmdpbi1ib3R0b206IC4xZW07IH1cbiAgLmZvcm11bGF0ZS1pbnB1dCAuZm9ybXVsYXRlLWlucHV0LWVsZW1lbnQge1xuICAgIG1heC13aWR0aDogMjBlbTtcbiAgICBtYXJnaW4tYm90dG9tOiAuMWVtOyB9XG4gIC5mb3JtdWxhdGUtaW5wdXQgLmZvcm11bGF0ZS1pbnB1dC1oZWxwIHtcbiAgICBjb2xvcjogIzZkNmQ2ZDtcbiAgICBmb250LXNpemU6IC43ZW07XG4gICAgZm9udC13ZWlnaHQ6IDMwMDtcbiAgICBsaW5lLWhlaWdodDogMS41O1xuICAgIG1hcmdpbi1ib3R0b206IC4yNWVtOyB9XG4gIC5mb3JtdWxhdGUtaW5wdXQgLmZvcm11bGF0ZS1pbnB1dC1ncm91cC1pdGVtIHtcbiAgICBtYXJnaW4tYm90dG9tOiAuNWVtOyB9XG4gIC5mb3JtdWxhdGUtaW5wdXQ6bGFzdC1jaGlsZCB7XG4gICAgbWFyZ2luLWJvdHRvbTogMDsgfVxuICAuZm9ybXVsYXRlLWlucHV0W2RhdGEtY2xhc3NpZmljYXRpb249J3RleHQnXSBpbnB1dCB7XG4gICAgYXBwZWFyYW5jZTogbm9uZTtcbiAgICBib3JkZXItcmFkaXVzOiAuM2VtO1xuICAgIGJvcmRlcjogMXB4IHNvbGlkICNjZWNlY2U7XG4gICAgYm94LXNpemluZzogYm9yZGVyLWJveDtcbiAgICBiYWNrZ3JvdW5kLWNvbG9yOiB0cmFuc3BhcmVudDtcbiAgICBmb250LXNpemU6IC45ZW07XG4gICAgZm9udC1mYW1pbHk6IC1hcHBsZS1zeXN0ZW0sIEJsaW5rTWFjU3lzdGVtRm9udCwgXCJTZWdvZSBVSVwiLCBSb2JvdG8sIEhlbHZldGljYSwgQXJpYWwsIHNhbnMtc2VyaWYsIFwiQXBwbGUgQ29sb3IgRW1vamlcIiwgXCJTZWdvZSBVSSBFbW9qaVwiLCBcIlNlZ29lIFVJIFN5bWJvbFwiO1xuICAgIHBhZGRpbmc6IC43NWVtO1xuICAgIGRpc3BsYXk6IGJsb2NrO1xuICAgIHdpZHRoOiAxMDAlO1xuICAgIGZvbnQtd2VpZ2h0OiA0MDA7IH1cbiAgICAuZm9ybXVsYXRlLWlucHV0W2RhdGEtY2xhc3NpZmljYXRpb249J3RleHQnXSBpbnB1dDo6cGxhY2Vob2xkZXIge1xuICAgICAgY29sb3I6ICNhOGE4YTg7IH1cbiAgICAuZm9ybXVsYXRlLWlucHV0W2RhdGEtY2xhc3NpZmljYXRpb249J3RleHQnXSBpbnB1dDpmb2N1cyB7XG4gICAgICBvdXRsaW5lOiAwO1xuICAgICAgYm9yZGVyOiAxcHggc29saWQgIzQxYjg4MzsgfVxuICAuZm9ybXVsYXRlLWlucHV0W2RhdGEtY2xhc3NpZmljYXRpb249J3RleHRhcmVhJ10gdGV4dGFyZWEge1xuICAgIGFwcGVhcmFuY2U6IG5vbmU7XG4gICAgYm9yZGVyLXJhZGl1czogLjNlbTtcbiAgICBib3JkZXI6IDFweCBzb2xpZCAjY2VjZWNlO1xuICAgIGJveC1zaXppbmc6IGJvcmRlci1ib3g7XG4gICAgYmFja2dyb3VuZC1jb2xvcjogdHJhbnNwYXJlbnQ7XG4gICAgZm9udC1zaXplOiAuOWVtO1xuICAgIGZvbnQtZmFtaWx5OiAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsIFwiU2Vnb2UgVUlcIiwgUm9ib3RvLCBIZWx2ZXRpY2EsIEFyaWFsLCBzYW5zLXNlcmlmLCBcIkFwcGxlIENvbG9yIEVtb2ppXCIsIFwiU2Vnb2UgVUkgRW1vamlcIiwgXCJTZWdvZSBVSSBTeW1ib2xcIjtcbiAgICBwYWRkaW5nOiAuNzVlbTtcbiAgICBkaXNwbGF5OiBibG9jaztcbiAgICB3aWR0aDogMTAwJTtcbiAgICBmb250LXdlaWdodDogNDAwOyB9XG4gICAgLmZvcm11bGF0ZS1pbnB1dFtkYXRhLWNsYXNzaWZpY2F0aW9uPSd0ZXh0YXJlYSddIHRleHRhcmVhOjpwbGFjZWhvbGRlciB7XG4gICAgICBjb2xvcjogI2E4YThhODsgfVxuICAgIC5mb3JtdWxhdGUtaW5wdXRbZGF0YS1jbGFzc2lmaWNhdGlvbj0ndGV4dGFyZWEnXSB0ZXh0YXJlYTpmb2N1cyB7XG4gICAgICBvdXRsaW5lOiAwO1xuICAgICAgYm9yZGVyOiAxcHggc29saWQgIzQxYjg4MzsgfVxuICAuZm9ybXVsYXRlLWlucHV0W2RhdGEtY2xhc3NpZmljYXRpb249J3NlbGVjdCddIC5mb3JtdWxhdGUtaW5wdXQtZWxlbWVudCB7XG4gICAgcG9zaXRpb246IHJlbGF0aXZlOyB9XG4gICAgLmZvcm11bGF0ZS1pbnB1dFtkYXRhLWNsYXNzaWZpY2F0aW9uPSdzZWxlY3QnXSAuZm9ybXVsYXRlLWlucHV0LWVsZW1lbnQ6OmJlZm9yZSB7XG4gICAgICBjb250ZW50OiAnJztcbiAgICAgIHdpZHRoOiAwO1xuICAgICAgaGVpZ2h0OiAwO1xuICAgICAgYm9yZGVyOiAuM2VtIHNvbGlkIHRyYW5zcGFyZW50O1xuICAgICAgYm9yZGVyLXRvcC1jb2xvcjogI2NlY2VjZTtcbiAgICAgIGJvcmRlci1ib3R0b20td2lkdGg6IDA7XG4gICAgICB0b3A6IDUwJTtcbiAgICAgIG1hcmdpbi10b3A6IC0uMWVtO1xuICAgICAgcmlnaHQ6IDFlbTtcbiAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTsgfVxuICAuZm9ybXVsYXRlLWlucHV0W2RhdGEtY2xhc3NpZmljYXRpb249J3NlbGVjdCddIHNlbGVjdCB7XG4gICAgYXBwZWFyYW5jZTogbm9uZTtcbiAgICBib3JkZXItcmFkaXVzOiAuM2VtO1xuICAgIGJvcmRlcjogMXB4IHNvbGlkICNjZWNlY2U7XG4gICAgYm94LXNpemluZzogYm9yZGVyLWJveDtcbiAgICBiYWNrZ3JvdW5kLWNvbG9yOiB0cmFuc3BhcmVudDtcbiAgICBmb250LXNpemU6IC45ZW07XG4gICAgZm9udC1mYW1pbHk6IC1hcHBsZS1zeXN0ZW0sIEJsaW5rTWFjU3lzdGVtRm9udCwgXCJTZWdvZSBVSVwiLCBSb2JvdG8sIEhlbHZldGljYSwgQXJpYWwsIHNhbnMtc2VyaWYsIFwiQXBwbGUgQ29sb3IgRW1vamlcIiwgXCJTZWdvZSBVSSBFbW9qaVwiLCBcIlNlZ29lIFVJIFN5bWJvbFwiO1xuICAgIHBhZGRpbmc6IC43NWVtO1xuICAgIGRpc3BsYXk6IGJsb2NrO1xuICAgIHdpZHRoOiAxMDAlO1xuICAgIGZvbnQtd2VpZ2h0OiA0MDA7XG4gICAgcGFkZGluZy1yaWdodDogMmVtOyB9XG4gICAgLmZvcm11bGF0ZS1pbnB1dFtkYXRhLWNsYXNzaWZpY2F0aW9uPSdzZWxlY3QnXSBzZWxlY3Q6OnBsYWNlaG9sZGVyIHtcbiAgICAgIGNvbG9yOiAjYThhOGE4OyB9XG4gICAgLmZvcm11bGF0ZS1pbnB1dFtkYXRhLWNsYXNzaWZpY2F0aW9uPSdzZWxlY3QnXSBzZWxlY3Q6Zm9jdXMge1xuICAgICAgb3V0bGluZTogMDtcbiAgICAgIGJvcmRlcjogMXB4IHNvbGlkICM0MWI4ODM7IH1cbiAgICAuZm9ybXVsYXRlLWlucHV0W2RhdGEtY2xhc3NpZmljYXRpb249J3NlbGVjdCddIHNlbGVjdFtkYXRhLXBsYWNlaG9sZGVyLXNlbGVjdGVkXSB7XG4gICAgICBjb2xvcjogI2E4YThhODsgfVxuICAuZm9ybXVsYXRlLWlucHV0W2RhdGEtY2xhc3NpZmljYXRpb249J2JveCddIC5mb3JtdWxhdGUtaW5wdXQtd3JhcHBlciB7XG4gICAgZGlzcGxheTogZmxleDtcbiAgICBhbGlnbi1pdGVtczogY2VudGVyOyB9XG4gIC5mb3JtdWxhdGUtaW5wdXRbZGF0YS1jbGFzc2lmaWNhdGlvbj0nYm94J10gLmZvcm11bGF0ZS1pbnB1dC1lbGVtZW50IHtcbiAgICBvdmVyZmxvdzogaGlkZGVuO1xuICAgIGRpc3BsYXk6IGZsZXg7XG4gICAgYWxpZ24taXRlbXM6IGNlbnRlcjsgfVxuICAgIC5mb3JtdWxhdGUtaW5wdXRbZGF0YS1jbGFzc2lmaWNhdGlvbj0nYm94J10gLmZvcm11bGF0ZS1pbnB1dC1lbGVtZW50IGlucHV0IHtcbiAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICAgIGxlZnQ6IC05OTlweDsgfVxuICAgIC5mb3JtdWxhdGUtaW5wdXRbZGF0YS1jbGFzc2lmaWNhdGlvbj0nYm94J10gLmZvcm11bGF0ZS1pbnB1dC1lbGVtZW50LWRlY29yYXRvciB7XG4gICAgICBkaXNwbGF5OiBibG9jaztcbiAgICAgIHdpZHRoOiAxZW07XG4gICAgICBoZWlnaHQ6IDFlbTtcbiAgICAgIGJvcmRlci1yYWRpdXM6IC4yNWVtO1xuICAgICAgYm9yZGVyOiAxcHggc29saWQgI2NlY2VjZTtcbiAgICAgIHBvc2l0aW9uOiByZWxhdGl2ZTsgfVxuICAgICAgLmZvcm11bGF0ZS1pbnB1dFtkYXRhLWNsYXNzaWZpY2F0aW9uPSdib3gnXSAuZm9ybXVsYXRlLWlucHV0LWVsZW1lbnQtZGVjb3JhdG9yOjpiZWZvcmUge1xuICAgICAgICBjb250ZW50OiAnJztcbiAgICAgICAgZGlzcGxheTogYmxvY2s7XG4gICAgICAgIGJhY2tncm91bmQtc2l6ZTogY29udGFpbjtcbiAgICAgICAgYmFja2dyb3VuZC1wb3NpdGlvbjogcmlnaHQ7XG4gICAgICAgIHdpZHRoOiBjYWxjKDEwMCUgLSAuMTI1ZW0pO1xuICAgICAgICBoZWlnaHQ6IGNhbGMoMTAwJSAtIC4xMjVlbSk7XG4gICAgICAgIGJveC1zaXppbmc6IGJvcmRlci1ib3g7XG4gICAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICAgICAgdG9wOiAuMDYyNWVtO1xuICAgICAgICBsZWZ0OiAuMDYyNWVtOyB9XG4gICAgLmZvcm11bGF0ZS1pbnB1dFtkYXRhLWNsYXNzaWZpY2F0aW9uPSdib3gnXSAuZm9ybXVsYXRlLWlucHV0LWVsZW1lbnRbZGF0YS10eXBlPVwicmFkaW9cIl0gLmZvcm11bGF0ZS1pbnB1dC1lbGVtZW50LWRlY29yYXRvciB7XG4gICAgICBib3JkZXItcmFkaXVzOiAxZW07IH1cbiAgICAgIC5mb3JtdWxhdGUtaW5wdXRbZGF0YS1jbGFzc2lmaWNhdGlvbj0nYm94J10gLmZvcm11bGF0ZS1pbnB1dC1lbGVtZW50W2RhdGEtdHlwZT1cInJhZGlvXCJdIC5mb3JtdWxhdGUtaW5wdXQtZWxlbWVudC1kZWNvcmF0b3I6OmJlZm9yZSB7XG4gICAgICAgIGJvcmRlci1yYWRpdXM6IDFlbTtcbiAgICAgICAgd2lkdGg6IGNhbGMoMTAwJSAtIC41ZW0pO1xuICAgICAgICBoZWlnaHQ6IGNhbGMoMTAwJSAtIC41ZW0pO1xuICAgICAgICB0b3A6IC4yNWVtO1xuICAgICAgICBsZWZ0OiAuMjVlbTsgfVxuICAgIC5mb3JtdWxhdGUtaW5wdXRbZGF0YS1jbGFzc2lmaWNhdGlvbj0nYm94J10gLmZvcm11bGF0ZS1pbnB1dC1lbGVtZW50IGlucHV0W3R5cGU9XCJjaGVja2JveFwiXTpjaGVja2VkIH4gLmZvcm11bGF0ZS1pbnB1dC1lbGVtZW50LWRlY29yYXRvciB7XG4gICAgICBib3JkZXItY29sb3I6ICM0MWI4ODM7IH1cbiAgICAgIC5mb3JtdWxhdGUtaW5wdXRbZGF0YS1jbGFzc2lmaWNhdGlvbj0nYm94J10gLmZvcm11bGF0ZS1pbnB1dC1lbGVtZW50IGlucHV0W3R5cGU9XCJjaGVja2JveFwiXTpjaGVja2VkIH4gLmZvcm11bGF0ZS1pbnB1dC1lbGVtZW50LWRlY29yYXRvcjo6YmVmb3JlIHtcbiAgICAgICAgYmFja2dyb3VuZC1pbWFnZTogdXJsKCdkYXRhOmltYWdlL3N2Zyt4bWw7dXRmOCw8c3ZnIHhtbG5zPVwiaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmdcIiB2aWV3Qm94PVwiMCAwIDEwMCAxMDBcIiBmaWxsPVwiJTIzNDFiODgzXCI+PHBhdGggZD1cIk04Ljc2LDU2LjJjLTYuMzgtNi4zNCwzLjI2LTE2LDkuNjQtOS42OUwzOCw2NS44OCw4MC41NiwyMy4yOWM2LjM4LTYuMzgsMTYuMDcsMy4zMiw5LjY5LDkuNjlMNDIuODQsODAuMzdhNi44Myw2LjgzLDAsMCwxLTkuNjUsMFpcIi8+PC9zdmc+Jyk7IH1cbiAgICAuZm9ybXVsYXRlLWlucHV0W2RhdGEtY2xhc3NpZmljYXRpb249J2JveCddIC5mb3JtdWxhdGUtaW5wdXQtZWxlbWVudCBpbnB1dFt0eXBlPVwicmFkaW9cIl06Y2hlY2tlZCB+IC5mb3JtdWxhdGUtaW5wdXQtZWxlbWVudC1kZWNvcmF0b3Ige1xuICAgICAgYm9yZGVyLWNvbG9yOiAjNDFiODgzOyB9XG4gICAgICAuZm9ybXVsYXRlLWlucHV0W2RhdGEtY2xhc3NpZmljYXRpb249J2JveCddIC5mb3JtdWxhdGUtaW5wdXQtZWxlbWVudCBpbnB1dFt0eXBlPVwicmFkaW9cIl06Y2hlY2tlZCB+IC5mb3JtdWxhdGUtaW5wdXQtZWxlbWVudC1kZWNvcmF0b3I6OmJlZm9yZSB7XG4gICAgICAgIGJhY2tncm91bmQtY29sb3I6ICM0MWI4ODM7IH1cbiAgICAuZm9ybXVsYXRlLWlucHV0W2RhdGEtY2xhc3NpZmljYXRpb249J2JveCddIC5mb3JtdWxhdGUtaW5wdXQtZWxlbWVudCBpbnB1dDpmb2N1cyB+IC5mb3JtdWxhdGUtaW5wdXQtZWxlbWVudC1kZWNvcmF0b3Ige1xuICAgICAgYm9yZGVyLWNvbG9yOiAjNDFiODgzOyB9XG4gIC5mb3JtdWxhdGUtaW5wdXRbZGF0YS1jbGFzc2lmaWNhdGlvbj0nYm94J10gLmZvcm11bGF0ZS1pbnB1dC1sYWJlbC0tYWZ0ZXIge1xuICAgIG1hcmdpbi1sZWZ0OiAuNWVtOyB9XG4gIC5mb3JtdWxhdGUtaW5wdXRbZGF0YS1jbGFzc2lmaWNhdGlvbj0nYm94J10gLmZvcm11bGF0ZS1pbnB1dC1sYWJlbC0tYmVmb3JlIHtcbiAgICBtYXJnaW4tcmlnaHQ6IC41ZW07IH1cbiAgLmZvcm11bGF0ZS1pbnB1dFtkYXRhLWNsYXNzaWZpY2F0aW9uPVwiZ3JvdXBcIl0gPiAuZm9ybXVsYXRlLWlucHV0LXdyYXBwZXIgPiAuZm9ybXVsYXRlLWlucHV0LWxhYmVsIHtcbiAgICBtYXJnaW4tYm90dG9tOiAuNWVtOyB9XG4iXX0= */ \ No newline at end of file +.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-errors{list-style-type:none;padding:0;margin:0}.formulate-input .formulate-input-error{color:#960505;font-size:.8em;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{-webkit-appearance:none;-moz-appearance:none;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::-webkit-input-placeholder{color:#a8a8a8}.formulate-input[data-classification=text] input::-moz-placeholder{color:#a8a8a8}.formulate-input[data-classification=text] input:-ms-input-placeholder{color:#a8a8a8}.formulate-input[data-classification=text] input::-ms-input-placeholder{color:#a8a8a8}.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{-webkit-appearance:none;-moz-appearance:none;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::-webkit-input-placeholder{color:#a8a8a8}.formulate-input[data-classification=textarea] textarea::-moz-placeholder{color:#a8a8a8}.formulate-input[data-classification=textarea] textarea:-ms-input-placeholder{color:#a8a8a8}.formulate-input[data-classification=textarea] textarea::-ms-input-placeholder{color:#a8a8a8}.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-color:#cecece transparent transparent;border-style:solid;border-width:.3em .3em 0;top:50%;margin-top:-.1em;right:1em;position:absolute}.formulate-input[data-classification=select] select{-webkit-appearance:none;-moz-appearance:none;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;display:block;width:100%;font-weight:400;padding:.75em 2em .75em .75em}.formulate-input[data-classification=select] select::-webkit-input-placeholder{color:#a8a8a8}.formulate-input[data-classification=select] select::-moz-placeholder{color:#a8a8a8}.formulate-input[data-classification=select] select:-ms-input-placeholder{color:#a8a8a8}.formulate-input[data-classification=select] select::-ms-input-placeholder{color:#a8a8a8}.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-element,.formulate-input[data-classification=box] .formulate-input-wrapper{display:-webkit-box;display:flex;-webkit-box-align:center;align-items:center}.formulate-input[data-classification=box] .formulate-input-element{overflow:hidden}.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:100%;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,')}.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} +/*# sourceMappingURL=data:application/json;base64,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SUFDRSxvQkFBdUIsQ0FDdkIsdUlBQ0Usd0JBQTJCLENBQy9CLGtIQUNFLG9CQUF1QixDQUMzQix3RUFDRSxnQkFBbUIsQ0FDckIseUVBQ0UsaUJBQW9CLENBQ3RCLDRGQUNFLGtCQUFxQiIsImZpbGUiOiJzdGRpbiIsInNvdXJjZXNDb250ZW50IjpbIi5mb3JtdWxhdGUtaW5wdXQge1xuICBtYXJnaW4tYm90dG9tOiAyZW07IH1cbiAgLmZvcm11bGF0ZS1pbnB1dCAuZm9ybXVsYXRlLWlucHV0LWxhYmVsIHtcbiAgICBkaXNwbGF5OiBibG9jaztcbiAgICBsaW5lLWhlaWdodDogMS41O1xuICAgIGZvbnQtc2l6ZTogLjllbTtcbiAgICBmb250LXdlaWdodDogNjAwO1xuICAgIG1hcmdpbi1ib3R0b206IC4xZW07IH1cbiAgLmZvcm11bGF0ZS1pbnB1dCAuZm9ybXVsYXRlLWlucHV0LWVsZW1lbnQge1xuICAgIG1heC13aWR0aDogMjBlbTtcbiAgICBtYXJnaW4tYm90dG9tOiAuMWVtOyB9XG4gIC5mb3JtdWxhdGUtaW5wdXQgLmZvcm11bGF0ZS1pbnB1dC1oZWxwIHtcbiAgICBjb2xvcjogIzZkNmQ2ZDtcbiAgICBmb250LXNpemU6IC43ZW07XG4gICAgZm9udC13ZWlnaHQ6IDMwMDtcbiAgICBsaW5lLWhlaWdodDogMS41O1xuICAgIG1hcmdpbi1ib3R0b206IC4yNWVtOyB9XG4gIC5mb3JtdWxhdGUtaW5wdXQgLmZvcm11bGF0ZS1pbnB1dC1lcnJvcnMge1xuICAgIGxpc3Qtc3R5bGUtdHlwZTogbm9uZTtcbiAgICBwYWRkaW5nOiAwO1xuICAgIG1hcmdpbjogMDsgfVxuICAuZm9ybXVsYXRlLWlucHV0IC5mb3JtdWxhdGUtaW5wdXQtZXJyb3Ige1xuICAgIGNvbG9yOiAjOTYwNTA1O1xuICAgIGZvbnQtc2l6ZTogLjhlbTtcbiAgICBmb250LXdlaWdodDogMzAwO1xuICAgIGxpbmUtaGVpZ2h0OiAxLjU7XG4gICAgbWFyZ2luLWJvdHRvbTogLjI1ZW07IH1cbiAgLmZvcm11bGF0ZS1pbnB1dCAuZm9ybXVsYXRlLWlucHV0LWdyb3VwLWl0ZW0ge1xuICAgIG1hcmdpbi1ib3R0b206IC41ZW07IH1cbiAgLmZvcm11bGF0ZS1pbnB1dDpsYXN0LWNoaWxkIHtcbiAgICBtYXJnaW4tYm90dG9tOiAwOyB9XG4gIC5mb3JtdWxhdGUtaW5wdXRbZGF0YS1jbGFzc2lmaWNhdGlvbj0ndGV4dCddIGlucHV0IHtcbiAgICBhcHBlYXJhbmNlOiBub25lO1xuICAgIGJvcmRlci1yYWRpdXM6IC4zZW07XG4gICAgYm9yZGVyOiAxcHggc29saWQgI2NlY2VjZTtcbiAgICBib3gtc2l6aW5nOiBib3JkZXItYm94O1xuICAgIGJhY2tncm91bmQtY29sb3I6IHRyYW5zcGFyZW50O1xuICAgIGZvbnQtc2l6ZTogLjllbTtcbiAgICBmb250LWZhbWlseTogLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCBcIlNlZ29lIFVJXCIsIFJvYm90bywgSGVsdmV0aWNhLCBBcmlhbCwgc2Fucy1zZXJpZiwgXCJBcHBsZSBDb2xvciBFbW9qaVwiLCBcIlNlZ29lIFVJIEVtb2ppXCIsIFwiU2Vnb2UgVUkgU3ltYm9sXCI7XG4gICAgcGFkZGluZzogLjc1ZW07XG4gICAgZGlzcGxheTogYmxvY2s7XG4gICAgd2lkdGg6IDEwMCU7XG4gICAgZm9udC13ZWlnaHQ6IDQwMDsgfVxuICAgIC5mb3JtdWxhdGUtaW5wdXRbZGF0YS1jbGFzc2lmaWNhdGlvbj0ndGV4dCddIGlucHV0OjpwbGFjZWhvbGRlciB7XG4gICAgICBjb2xvcjogI2E4YThhODsgfVxuICAgIC5mb3JtdWxhdGUtaW5wdXRbZGF0YS1jbGFzc2lmaWNhdGlvbj0ndGV4dCddIGlucHV0OmZvY3VzIHtcbiAgICAgIG91dGxpbmU6IDA7XG4gICAgICBib3JkZXI6IDFweCBzb2xpZCAjNDFiODgzOyB9XG4gIC5mb3JtdWxhdGUtaW5wdXRbZGF0YS1jbGFzc2lmaWNhdGlvbj0ndGV4dGFyZWEnXSB0ZXh0YXJlYSB7XG4gICAgYXBwZWFyYW5jZTogbm9uZTtcbiAgICBib3JkZXItcmFkaXVzOiAuM2VtO1xuICAgIGJvcmRlcjogMXB4IHNvbGlkICNjZWNlY2U7XG4gICAgYm94LXNpemluZzogYm9yZGVyLWJveDtcbiAgICBiYWNrZ3JvdW5kLWNvbG9yOiB0cmFuc3BhcmVudDtcbiAgICBmb250LXNpemU6IC45ZW07XG4gICAgZm9udC1mYW1pbHk6IC1hcHBsZS1zeXN0ZW0sIEJsaW5rTWFjU3lzdGVtRm9udCwgXCJTZWdvZSBVSVwiLCBSb2JvdG8sIEhlbHZldGljYSwgQXJpYWwsIHNhbnMtc2VyaWYsIFwiQXBwbGUgQ29sb3IgRW1vamlcIiwgXCJTZWdvZSBVSSBFbW9qaVwiLCBcIlNlZ29lIFVJIFN5bWJvbFwiO1xuICAgIHBhZGRpbmc6IC43NWVtO1xuICAgIGRpc3BsYXk6IGJsb2NrO1xuICAgIHdpZHRoOiAxMDAlO1xuICAgIGZvbnQtd2VpZ2h0OiA0MDA7IH1cbiAgICAuZm9ybXVsYXRlLWlucHV0W2RhdGEtY2xhc3NpZmljYXRpb249J3RleHRhcmVhJ10gdGV4dGFyZWE6OnBsYWNlaG9sZGVyIHtcbiAgICAgIGNvbG9yOiAjYThhOGE4OyB9XG4gICAgLmZvcm11bGF0ZS1pbnB1dFtkYXRhLWNsYXNzaWZpY2F0aW9uPSd0ZXh0YXJlYSddIHRleHRhcmVhOmZvY3VzIHtcbiAgICAgIG91dGxpbmU6IDA7XG4gICAgICBib3JkZXI6IDFweCBzb2xpZCAjNDFiODgzOyB9XG4gIC5mb3JtdWxhdGUtaW5wdXRbZGF0YS1jbGFzc2lmaWNhdGlvbj0nc2VsZWN0J10gLmZvcm11bGF0ZS1pbnB1dC1lbGVtZW50IHtcbiAgICBwb3NpdGlvbjogcmVsYXRpdmU7IH1cbiAgICAuZm9ybXVsYXRlLWlucHV0W2RhdGEtY2xhc3NpZmljYXRpb249J3NlbGVjdCddIC5mb3JtdWxhdGUtaW5wdXQtZWxlbWVudDo6YmVmb3JlIHtcbiAgICAgIGNvbnRlbnQ6ICcnO1xuICAgICAgd2lkdGg6IDA7XG4gICAgICBoZWlnaHQ6IDA7XG4gICAgICBib3JkZXI6IC4zZW0gc29saWQgdHJhbnNwYXJlbnQ7XG4gICAgICBib3JkZXItdG9wLWNvbG9yOiAjY2VjZWNlO1xuICAgICAgYm9yZGVyLWJvdHRvbS13aWR0aDogMDtcbiAgICAgIHRvcDogNTAlO1xuICAgICAgbWFyZ2luLXRvcDogLS4xZW07XG4gICAgICByaWdodDogMWVtO1xuICAgICAgcG9zaXRpb246IGFic29sdXRlOyB9XG4gIC5mb3JtdWxhdGUtaW5wdXRbZGF0YS1jbGFzc2lmaWNhdGlvbj0nc2VsZWN0J10gc2VsZWN0IHtcbiAgICBhcHBlYXJhbmNlOiBub25lO1xuICAgIGJvcmRlci1yYWRpdXM6IC4zZW07XG4gICAgYm9yZGVyOiAxcHggc29saWQgI2NlY2VjZTtcbiAgICBib3gtc2l6aW5nOiBib3JkZXItYm94O1xuICAgIGJhY2tncm91bmQtY29sb3I6IHRyYW5zcGFyZW50O1xuICAgIGZvbnQtc2l6ZTogLjllbTtcbiAgICBmb250LWZhbWlseTogLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCBcIlNlZ29lIFVJXCIsIFJvYm90bywgSGVsdmV0aWNhLCBBcmlhbCwgc2Fucy1zZXJpZiwgXCJBcHBsZSBDb2xvciBFbW9qaVwiLCBcIlNlZ29lIFVJIEVtb2ppXCIsIFwiU2Vnb2UgVUkgU3ltYm9sXCI7XG4gICAgcGFkZGluZzogLjc1ZW07XG4gICAgZGlzcGxheTogYmxvY2s7XG4gICAgd2lkdGg6IDEwMCU7XG4gICAgZm9udC13ZWlnaHQ6IDQwMDtcbiAgICBwYWRkaW5nLXJpZ2h0OiAyZW07IH1cbiAgICAuZm9ybXVsYXRlLWlucHV0W2RhdGEtY2xhc3NpZmljYXRpb249J3NlbGVjdCddIHNlbGVjdDo6cGxhY2Vob2xkZXIge1xuICAgICAgY29sb3I6ICNhOGE4YTg7IH1cbiAgICAuZm9ybXVsYXRlLWlucHV0W2RhdGEtY2xhc3NpZmljYXRpb249J3NlbGVjdCddIHNlbGVjdDpmb2N1cyB7XG4gICAgICBvdXRsaW5lOiAwO1xuICAgICAgYm9yZGVyOiAxcHggc29saWQgIzQxYjg4MzsgfVxuICAgIC5mb3JtdWxhdGUtaW5wdXRbZGF0YS1jbGFzc2lmaWNhdGlvbj0nc2VsZWN0J10gc2VsZWN0W2RhdGEtcGxhY2Vob2xkZXItc2VsZWN0ZWRdIHtcbiAgICAgIGNvbG9yOiAjYThhOGE4OyB9XG4gIC5mb3JtdWxhdGUtaW5wdXRbZGF0YS1jbGFzc2lmaWNhdGlvbj0nYm94J10gLmZvcm11bGF0ZS1pbnB1dC13cmFwcGVyIHtcbiAgICBkaXNwbGF5OiBmbGV4O1xuICAgIGFsaWduLWl0ZW1zOiBjZW50ZXI7IH1cbiAgLmZvcm11bGF0ZS1pbnB1dFtkYXRhLWNsYXNzaWZpY2F0aW9uPSdib3gnXSAuZm9ybXVsYXRlLWlucHV0LWVsZW1lbnQge1xuICAgIG92ZXJmbG93OiBoaWRkZW47XG4gICAgZGlzcGxheTogZmxleDtcbiAgICBhbGlnbi1pdGVtczogY2VudGVyOyB9XG4gICAgLmZvcm11bGF0ZS1pbnB1dFtkYXRhLWNsYXNzaWZpY2F0aW9uPSdib3gnXSAuZm9ybXVsYXRlLWlucHV0LWVsZW1lbnQgaW5wdXQge1xuICAgICAgcG9zaXRpb246IGFic29sdXRlO1xuICAgICAgbGVmdDogLTk5OXB4OyB9XG4gICAgLmZvcm11bGF0ZS1pbnB1dFtkYXRhLWNsYXNzaWZpY2F0aW9uPSdib3gnXSAuZm9ybXVsYXRlLWlucHV0LWVsZW1lbnQtZGVjb3JhdG9yIHtcbiAgICAgIGRpc3BsYXk6IGJsb2NrO1xuICAgICAgd2lkdGg6IDFlbTtcbiAgICAgIGhlaWdodDogMWVtO1xuICAgICAgYm9yZGVyLXJhZGl1czogLjI1ZW07XG4gICAgICBib3JkZXI6IDFweCBzb2xpZCAjY2VjZWNlO1xuICAgICAgcG9zaXRpb246IHJlbGF0aXZlOyB9XG4gICAgICAuZm9ybXVsYXRlLWlucHV0W2RhdGEtY2xhc3NpZmljYXRpb249J2JveCddIC5mb3JtdWxhdGUtaW5wdXQtZWxlbWVudC1kZWNvcmF0b3I6OmJlZm9yZSB7XG4gICAgICAgIGNvbnRlbnQ6ICcnO1xuICAgICAgICBkaXNwbGF5OiBibG9jaztcbiAgICAgICAgYmFja2dyb3VuZC1zaXplOiBjb250YWluO1xuICAgICAgICBiYWNrZ3JvdW5kLXBvc2l0aW9uOiByaWdodDtcbiAgICAgICAgd2lkdGg6IGNhbGMoMTAwJSAtIC4xMjVlbSk7XG4gICAgICAgIGhlaWdodDogY2FsYygxMDAlIC0gLjEyNWVtKTtcbiAgICAgICAgYm94LXNpemluZzogYm9yZGVyLWJveDtcbiAgICAgICAgcG9zaXRpb246IGFic29sdXRlO1xuICAgICAgICB0b3A6IC4wNjI1ZW07XG4gICAgICAgIGxlZnQ6IC4wNjI1ZW07IH1cbiAgICAuZm9ybXVsYXRlLWlucHV0W2RhdGEtY2xhc3NpZmljYXRpb249J2JveCddIC5mb3JtdWxhdGUtaW5wdXQtZWxlbWVudFtkYXRhLXR5cGU9XCJyYWRpb1wiXSAuZm9ybXVsYXRlLWlucHV0LWVsZW1lbnQtZGVjb3JhdG9yIHtcbiAgICAgIGJvcmRlci1yYWRpdXM6IDFlbTsgfVxuICAgICAgLmZvcm11bGF0ZS1pbnB1dFtkYXRhLWNsYXNzaWZpY2F0aW9uPSdib3gnXSAuZm9ybXVsYXRlLWlucHV0LWVsZW1lbnRbZGF0YS10eXBlPVwicmFkaW9cIl0gLmZvcm11bGF0ZS1pbnB1dC1lbGVtZW50LWRlY29yYXRvcjo6YmVmb3JlIHtcbiAgICAgICAgYm9yZGVyLXJhZGl1czogMWVtO1xuICAgICAgICB3aWR0aDogY2FsYygxMDAlIC0gLjVlbSk7XG4gICAgICAgIGhlaWdodDogY2FsYygxMDAlIC0gLjVlbSk7XG4gICAgICAgIHRvcDogLjI1ZW07XG4gICAgICAgIGxlZnQ6IC4yNWVtOyB9XG4gICAgLmZvcm11bGF0ZS1pbnB1dFtkYXRhLWNsYXNzaWZpY2F0aW9uPSdib3gnXSAuZm9ybXVsYXRlLWlucHV0LWVsZW1lbnQgaW5wdXRbdHlwZT1cImNoZWNrYm94XCJdOmNoZWNrZWQgfiAuZm9ybXVsYXRlLWlucHV0LWVsZW1lbnQtZGVjb3JhdG9yIHtcbiAgICAgIGJvcmRlci1jb2xvcjogIzQxYjg4MzsgfVxuICAgICAgLmZvcm11bGF0ZS1pbnB1dFtkYXRhLWNsYXNzaWZpY2F0aW9uPSdib3gnXSAuZm9ybXVsYXRlLWlucHV0LWVsZW1lbnQgaW5wdXRbdHlwZT1cImNoZWNrYm94XCJdOmNoZWNrZWQgfiAuZm9ybXVsYXRlLWlucHV0LWVsZW1lbnQtZGVjb3JhdG9yOjpiZWZvcmUge1xuICAgICAgICBiYWNrZ3JvdW5kLWltYWdlOiB1cmwoJ2RhdGE6aW1hZ2Uvc3ZnK3htbDt1dGY4LDxzdmcgeG1sbnM9XCJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2Z1wiIHZpZXdCb3g9XCIwIDAgMTAwIDEwMFwiIGZpbGw9XCIlMjM0MWI4ODNcIj48cGF0aCBkPVwiTTguNzYsNTYuMmMtNi4zOC02LjM0LDMuMjYtMTYsOS42NC05LjY5TDM4LDY1Ljg4LDgwLjU2LDIzLjI5YzYuMzgtNi4zOCwxNi4wNywzLjMyLDkuNjksOS42OUw0Mi44NCw4MC4zN2E2LjgzLDYuODMsMCwwLDEtOS42NSwwWlwiLz48L3N2Zz4nKTsgfVxuICAgIC5mb3JtdWxhdGUtaW5wdXRbZGF0YS1jbGFzc2lmaWNhdGlvbj0nYm94J10gLmZvcm11bGF0ZS1pbnB1dC1lbGVtZW50IGlucHV0W3R5cGU9XCJyYWRpb1wiXTpjaGVja2VkIH4gLmZvcm11bGF0ZS1pbnB1dC1lbGVtZW50LWRlY29yYXRvciB7XG4gICAgICBib3JkZXItY29sb3I6ICM0MWI4ODM7IH1cbiAgICAgIC5mb3JtdWxhdGUtaW5wdXRbZGF0YS1jbGFzc2lmaWNhdGlvbj0nYm94J10gLmZvcm11bGF0ZS1pbnB1dC1lbGVtZW50IGlucHV0W3R5cGU9XCJyYWRpb1wiXTpjaGVja2VkIH4gLmZvcm11bGF0ZS1pbnB1dC1lbGVtZW50LWRlY29yYXRvcjo6YmVmb3JlIHtcbiAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogIzQxYjg4MzsgfVxuICAgIC5mb3JtdWxhdGUtaW5wdXRbZGF0YS1jbGFzc2lmaWNhdGlvbj0nYm94J10gLmZvcm11bGF0ZS1pbnB1dC1lbGVtZW50IGlucHV0OmZvY3VzIH4gLmZvcm11bGF0ZS1pbnB1dC1lbGVtZW50LWRlY29yYXRvciB7XG4gICAgICBib3JkZXItY29sb3I6ICM0MWI4ODM7IH1cbiAgLmZvcm11bGF0ZS1pbnB1dFtkYXRhLWNsYXNzaWZpY2F0aW9uPSdib3gnXSAuZm9ybXVsYXRlLWlucHV0LWxhYmVsLS1hZnRlciB7XG4gICAgbWFyZ2luLWxlZnQ6IC41ZW07IH1cbiAgLmZvcm11bGF0ZS1pbnB1dFtkYXRhLWNsYXNzaWZpY2F0aW9uPSdib3gnXSAuZm9ybXVsYXRlLWlucHV0LWxhYmVsLS1iZWZvcmUge1xuICAgIG1hcmdpbi1yaWdodDogLjVlbTsgfVxuICAuZm9ybXVsYXRlLWlucHV0W2RhdGEtY2xhc3NpZmljYXRpb249XCJncm91cFwiXSA+IC5mb3JtdWxhdGUtaW5wdXQtd3JhcHBlciA+IC5mb3JtdWxhdGUtaW5wdXQtbGFiZWwge1xuICAgIG1hcmdpbi1ib3R0b206IC41ZW07IH1cbiJdfQ== */ \ No newline at end of file diff --git a/src/Formulate.js b/src/Formulate.js index f092c5b..cf8dde0 100644 --- a/src/Formulate.js +++ b/src/Formulate.js @@ -2,6 +2,7 @@ import library from './libs/library' import isPlainObject from 'is-plain-object' import FormulateInput from './FormulateInput.vue' import FormulateForm from './FormulateForm.vue' +import FormulateInputErrors from './FormulateInputErrors.vue' import FormulateInputGroup from './FormulateInputGroup.vue' import FormulateInputBox from './inputs/FormulateInputBox.vue' import FormulateInputText from './inputs/FormulateInputText.vue' @@ -19,6 +20,7 @@ class Formulate { components: { FormulateForm, FormulateInput, + FormulateInputErrors, FormulateInputBox, FormulateInputText, FormulateInputGroup, diff --git a/src/FormulateInput.vue b/src/FormulateInput.vue index d6b5994..9a278bf 100644 --- a/src/FormulateInput.vue +++ b/src/FormulateInput.vue @@ -2,6 +2,7 @@
@@ -39,6 +40,9 @@ class="formulate-input-help" v-text="help" /> +
@@ -102,6 +106,25 @@ export default { debug: { type: Boolean, default: false + }, + errors: { + type: [String, Array, Boolean], + default: false + }, + validation: { + type: [String, Boolean, Array], + default: false + }, + validationBehavior: { + type: String, + default: 'blur', + validator: function (value) { + return ['blur', 'live'].includes(value) + } + }, + error: { + type: [String, Boolean], + default: false } }, data () { diff --git a/src/FormulateInputErrors.vue b/src/FormulateInputErrors.vue new file mode 100644 index 0000000..8ed7270 --- /dev/null +++ b/src/FormulateInputErrors.vue @@ -0,0 +1,26 @@ + + + diff --git a/src/libs/context.js b/src/libs/context.js index 7431fdc..0cfa813 100644 --- a/src/libs/context.js +++ b/src/libs/context.js @@ -1,5 +1,5 @@ import nanoid from 'nanoid' -import { map } from './utils' +import { map, arrayify, parseRules } from './utils' /** * For a single instance of an input, export all of the context needed to fully @@ -28,7 +28,10 @@ export default { typeContext, elementAttributes, logicalLabelPosition, - isVmodeled + isVmodeled, + mergedErrors, + hasErrors, + validationRules } /** @@ -129,6 +132,30 @@ function createOptionList (options) { return options } +/** + * The merged errors computed property. + */ +function mergedErrors () { + return arrayify(this.errors) + .concat(arrayify(this.error)) + .concat(arrayify(this.validationErrors)) + .reduce((errors, err) => !errors.includes(err) ? errors.concat(err) : errors, []) +} + +/** + * Does this computed property have errors. + */ +function hasErrors () { + return !!this.mergedErrors.length +} + +/** + * An array of validation rules to pass. + */ +function validationRules () { + return parseRules(this.validation) +} + /** * Defines the model used throughout the existing context. * @param {object} context diff --git a/src/libs/rules.js b/src/libs/rules.js new file mode 100644 index 0000000..11a09f5 --- /dev/null +++ b/src/libs/rules.js @@ -0,0 +1,41 @@ +import { shallowEqualObjects } from './utils' + +/** + * Library of rules + */ +export default { + /** + * Rule: must be a value + */ + required: async function (value) { + if (Array.isArray(value)) { + return !!value.length + } + if (typeof value === 'string') { + return !!value + } + if (typeof value === 'object') { + return (!value) ? false : !!Object.keys(value).length + } + return true + }, + + /** + * Rule: Value is in an array (stack). + */ + in: async function (value, ...stack) { + return !!stack.find(item => shallowEqualObjects(item, value)) + }, + + /** + * Rule: Match the value against a (stack) of patterns or strings + */ + matches: async function (value, ...stack) { + return !!stack.find(pattern => { + if (pattern instanceof RegExp) { + return pattern.test(value) + } + return pattern === value + }) + } +} diff --git a/src/libs/utils.js b/src/libs/utils.js index f966686..636ea41 100644 --- a/src/libs/utils.js +++ b/src/libs/utils.js @@ -69,3 +69,68 @@ export function shallowEqualObjects (objA, objB) { } return true } + +/** + * Given a string, object, falsey, or array - return an array. + * @param {mixed} item + */ +export function arrayify (item) { + if (!item) { + return [] + } + if (typeof item === 'string') { + return [item] + } + if (Array.isArray(item)) { + return item + } + if (typeof item === 'object') { + return Object.values(item) + } + return [] +} + +/** + * Given an array or string return an array of callables. + * @param {array|string} validation + * @param {array} rules and array of functions + * @return {array} an array of functions + */ +export function parseRules (validation, rules) { + if (typeof validation === 'string') { + return parseRules(validation.split('|'), rules) + } + if (!Array.isArray(validation)) { + return [] + } + return validation.map(rule => parseRule(rule, rules)).filter(f => !!f) +} + +/** + * Given a string or function, parse it and return the an array in the format + * [fn, [...arguments]] + * @param {string|function} rule + */ +function parseRule (rule, rules) { + if (typeof rule === 'function') { + return [rule, []] + } + if (Array.isArray(rule) && rule.length) { + if (typeof rule[0] === 'string' && rules.hasOwnProperty(rule[0])) { + return [rules[rule.shift()], rule] + } + if (typeof rule[0] === 'function') { + return [rule.shift(), rule] + } + } + if (typeof rule === 'string') { + const segments = rule.split(':') + const functionName = segments.shift() + if (rules.hasOwnProperty(functionName)) { + return [rules[functionName], segments.length ? segments.join(':').split(',') : []] + } else { + throw new Error(`Unknown validation rule ${rule}`) + } + } + return false +} diff --git a/test/Formulate.test.js b/test/Formulate.test.js index 851a034..bc905ac 100644 --- a/test/Formulate.test.js +++ b/test/Formulate.test.js @@ -50,6 +50,7 @@ test('installs on vue instance', () => { const components = [ 'FormulateForm', 'FormulateInput', + 'FormulateInputErrors', 'FormulateInputBox', 'FormulateInputText', 'FormulateInputGroup', diff --git a/test/FormulateInputText.test.js b/test/FormulateInputText.test.js index 4b47736..7b1eba2 100644 --- a/test/FormulateInputText.test.js +++ b/test/FormulateInputText.test.js @@ -11,145 +11,176 @@ 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 rendering 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.context.attributes.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.context.attributes.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) -}) - -/** - * Test data binding - */ -test('text inputs emit input (vmodel) event with value when edited', () => { - const wrapper = mount(FormulateInput, { propsData: { type: 'text' } }) - wrapper.find('input').setValue('Updated Value') - expect(wrapper.emitted().input).toBeTruthy() - expect(wrapper.emitted().input[0]).toEqual(['Updated Value']) -}) - - -test('test that inputs that arent updated dont re-context themselves', () => { - const wrapper = mount({ - data () { - return { - valueA: 'first value', - valueB: 'second value' - } - }, - template: ` -
- - -
- ` +describe('FormulateInputText', () => { + it('renders text input when type is "text"', () => { + const wrapper = mount(FormulateInput, { propsData: { type: 'text' } }) + expect(wrapper.contains(FormulateInputText)).toBe(true) }) - const firstContext = wrapper.find({ref: "first"}).vm.context - const secondContext = wrapper.find({ref: "second"}).vm.context - wrapper.find('input').setValue('new value') - expect(firstContext).toBeTruthy() - expect(wrapper.vm.valueA === 'new value').toBe(true) - expect(wrapper.vm.valueB === 'second value').toBe(true) - expect(wrapper.find({ref: "first"}).vm.context === firstContext).toBe(false) - expect(wrapper.find({ref: "second"}).vm.context === secondContext).toBe(true) -}) -test('test that inputs contain their v-model value as the initial input', () => { - const wrapper = mount(FormulateInput, { propsData: { type: 'text', formulateValue: 'initial val' } }) - expect(wrapper.find('input').element.value).toBe('initial val') -}) + it('renders search input when type is "search"', () => { + const wrapper = mount(FormulateInput, { propsData: { type: 'search' } }) + expect(wrapper.contains(FormulateInputText)).toBe(true) + }) -test('test that inputs without v-model set a proxy model', () => { - const wrapper = mount(FormulateInput, { propsData: { type: 'textarea' } }) - const input = wrapper.find('textarea') - input.setValue('changed value') - expect(wrapper.vm.internalModelProxy).toBe('changed value') + it('renders email input when type is "email"', () => { + const wrapper = mount(FormulateInput, { propsData: { type: 'email' } }) + expect(wrapper.contains(FormulateInputText)).toBe(true) + }) + + it('renders number input when type is "number"', () => { + const wrapper = mount(FormulateInput, { propsData: { type: 'number' } }) + expect(wrapper.contains(FormulateInputText)).toBe(true) + }) + + it('renders color input when type is "color"', () => { + const wrapper = mount(FormulateInput, { propsData: { type: 'color' } }) + expect(wrapper.contains(FormulateInputText)).toBe(true) + }) + + it('renders date input when type is "date"', () => { + const wrapper = mount(FormulateInput, { propsData: { type: 'date' } }) + expect(wrapper.contains(FormulateInputText)).toBe(true) + }) + + it('renders month input when type is "month"', () => { + const wrapper = mount(FormulateInput, { propsData: { type: 'month' } }) + expect(wrapper.contains(FormulateInputText)).toBe(true) + }) + + it('renders password input when type is "password"', () => { + const wrapper = mount(FormulateInput, { propsData: { type: 'password' } }) + expect(wrapper.contains(FormulateInputText)).toBe(true) + }) + + it('renders range input when type is "range"', () => { + const wrapper = mount(FormulateInput, { propsData: { type: 'range' } }) + expect(wrapper.contains(FormulateInputText)).toBe(true) + }) + + it('renders tel input when type is "tel"', () => { + const wrapper = mount(FormulateInput, { propsData: { type: 'tel' } }) + expect(wrapper.contains(FormulateInputText)).toBe(true) + }) + + it('renders time input when type is "time"', () => { + const wrapper = mount(FormulateInput, { propsData: { type: 'time' } }) + expect(wrapper.contains(FormulateInputText)).toBe(true) + }) + + it('renders url input when type is "url"', () => { + const wrapper = mount(FormulateInput, { propsData: { type: 'url' } }) + expect(wrapper.contains(FormulateInputText)).toBe(true) + }) + + it('renders week input when type is "week"', () => { + const wrapper = mount(FormulateInput, { propsData: { type: 'week' } }) + expect(wrapper.contains(FormulateInputText)).toBe(true) + }) + + /** + * Test rendering functionality to text inputs + */ + + it('automatically assigns an id', () => { + const wrapper = mount(FormulateInput, { propsData: { type: 'text' } }) + expect(wrapper.vm.context).toHaveProperty('id') + expect(wrapper.find(`input[id="${wrapper.vm.context.attributes.id}"]`).exists()).toBe(true) + }) + + it('doesn’t automatically add a label', () => { + const wrapper = mount(FormulateInput, { propsData: { type: 'text' } }) + expect(wrapper.find('label').exists()).toBe(false) + }) + + it('renders labels', () => { + const wrapper = mount(FormulateInput, { propsData: { type: 'text', label: 'Field label' } }) + expect(wrapper.find(`label[for="${wrapper.vm.context.attributes.id}"]`).exists()).toBe(true) + }) + + it('doesn’t automatically render help text', () => { + const wrapper = mount(FormulateInput, { propsData: { type: 'text' } }) + expect(wrapper.find(`.formulate-input-help`).exists()).toBe(false) + }) + + it('renders help text', () => { + const wrapper = mount(FormulateInput, { propsData: { type: 'text', help: 'This is some help text' } }) + expect(wrapper.find(`.formulate-input-help`).exists()).toBe(true) + }) + + /** + * Test data binding + */ + it('emits input (vmodel) event with value when edited', () => { + const wrapper = mount(FormulateInput, { propsData: { type: 'text' } }) + wrapper.find('input').setValue('Updated Value') + expect(wrapper.emitted().input).toBeTruthy() + expect(wrapper.emitted().input[0]).toEqual(['Updated Value']) + }) + + + it('doesn’t re-context itself if there were no changes', () => { + const wrapper = mount({ + data () { + return { + valueA: 'first value', + valueB: 'second value' + } + }, + template: ` +
+ + +
+ ` + }) + const firstContext = wrapper.find({ref: "first"}).vm.context + const secondContext = wrapper.find({ref: "second"}).vm.context + wrapper.find('input').setValue('new value') + expect(firstContext).toBeTruthy() + expect(wrapper.vm.valueA === 'new value').toBe(true) + expect(wrapper.vm.valueB === 'second value').toBe(true) + expect(wrapper.find({ref: "first"}).vm.context === firstContext).toBe(false) + expect(wrapper.find({ref: "second"}).vm.context === secondContext).toBe(true) + }) + + it('uses the v-model value as the initial value', () => { + const wrapper = mount(FormulateInput, { propsData: { type: 'text', formulateValue: 'initial val' } }) + expect(wrapper.find('input').element.value).toBe('initial val') + }) + + it('uses a proxy model internally if it doesnt have a v-model', () => { + const wrapper = mount(FormulateInput, { propsData: { type: 'textarea' } }) + const input = wrapper.find('textarea') + input.setValue('changed value') + expect(wrapper.vm.internalModelProxy).toBe('changed value') + }) + + + /** + * Test error handling + */ + it('doesn’t automatically render errors', () => { + const wrapper = mount(FormulateInput, { propsData: { type: 'text' } }) + expect(wrapper.find('.formulate-input-errors').exists()).toBe(false) + }) + + it('accepts a single string as an error prop', () => { + const wrapper = mount(FormulateInput, { propsData: { type: 'text', error: 'This is an error' } }) + expect(wrapper.find('.formulate-input-errors').exists()).toBe(true) + }) + + it('accepts an array as errors prop', () => { + const wrapper = mount(FormulateInput, { propsData: { type: 'text', errors: ['This is an error', 'this is another'] } }) + expect(wrapper.findAll('.formulate-input-error').length).toBe(2) + }) + + it('removes any duplicate errors', () => { + const wrapper = mount(FormulateInput, { propsData: { type: 'text', errors: ['This is an error', 'This is an error'] } }) + expect(wrapper.findAll('.formulate-input-error').length).toBe(1) + }) + + it('adds data-has-errors when there are errors', () => { + const wrapper = mount(FormulateInput, { propsData: { type: 'text', errors: ['This is an error', 'This is an error'] } }) + expect(wrapper.find('[data-has-errors]').exists()).toBe(true) + }) }) diff --git a/test/rules.test.js b/test/rules.test.js new file mode 100644 index 0000000..9259f53 --- /dev/null +++ b/test/rules.test.js @@ -0,0 +1,77 @@ +import rules from '@/libs/rules' + +/** + * Required rule + */ +describe('required', () => { + it('fails on empty string', async () => expect(await rules.required('')).toBe(false)) + + it('fails on empty array', async () => expect(await rules.required([])).toBe(false)) + + it('fails on empty object', async () => expect(await rules.required({})).toBe(false)) + + it('fails on null', async () => expect(await rules.required(null)).toBe(false)) + + it('passes with the number zero', async () => expect(await rules.required(0)).toBe(true)) + + it('passes with the boolean false', async () => expect(await rules.required(false)).toBe(true)) + + it('passes with a non empty array', async () => expect(await rules.required(['123'])).toBe(true)) + + it('passes with a non empty object', async () => expect(await rules.required({a: 'b'})).toBe(true)) +}) + + +/** + * In rule + */ +describe('in', () => { + it('fails when not in stack', async () => { + expect(await rules.in('third', 'first', 'second')).toBe(false) + }) + + it('fails when case sensitive mismatch is in stack', async () => { + expect(await rules.in('third', 'first', 'second', 'Third')).toBe(false) + }) + + it('fails comparing dissimilar objects', async () => { + expect(await rules.in({f: 'abc'}, {a: 'cdf'}, {b: 'abc'})).toBe(false) + }) + + it('passes when case sensitive match is in stack', async () => { + expect(await rules.in('third', 'first', 'second', 'third')).toBe(true) + }) + + it('passes a shallow array compare', async () => { + expect(await rules.in(['abc'], ['cdf'], ['abc'])).toBe(true) + }) + + it('passes a shallow object compare', async () => { + expect(await rules.in({f: 'abc'}, {a: 'cdf'}, {f: 'abc'},)).toBe(true) + }) +}) + +/** + * Matches rule + */ +describe('matches', () => { + it('simple strings fail if they aren’t equal', async () => { + expect(await rules.matches('third', 'first')).toBe(false) + }) + + it('fails on non matching regex', async () => { + expect(await rules.matches('third', /^thirds/)).toBe(false) + }) + + it('passes if simple strings match', async () => { + expect(await rules.matches('second', 'third', 'second')).toBe(true) + }) + + it('passes on matching regex', async () => { + expect(await rules.matches('third', /^third/)).toBe(true) + }) + + it('passes on matching mixed regex and string', async () => { + expect(await rules.matches('first-fourth', 'second', /^third/, /fourth$/)).toBe(true) + }) +}) diff --git a/test/utils.test.js b/test/utils.test.js new file mode 100644 index 0000000..e5da34a --- /dev/null +++ b/test/utils.test.js @@ -0,0 +1,44 @@ +import { parseRules } from '@/libs/utils' +import rules from '@/libs/rules' + +describe('parseRules', () => { + it('parses single string rules, returning empty arguments array', () => { + expect(parseRules('required', rules)).toEqual([ + [rules.required, []] + ]) + }) + + it('throws errors for invalid validation rules', () => { + expect(() => { + parseRules('required|notarule', rules) + }).toThrow() + }) + + it('parses arguments for a rule', () => { + expect(parseRules('in:foo,bar', rules)).toEqual([ + [rules.in, ['foo', 'bar']] + ]) + }) + + it('parses multiple string rules and arguments', () => { + expect(parseRules('required|in:foo,bar', rules)).toEqual([ + [rules.required, []], + [rules.in, ['foo', 'bar']] + ]) + }) + + it('parses multiple array rules and arguments', () => { + expect(parseRules(['required', 'in:foo,bar'], rules)).toEqual([ + [rules.required, []], + [rules.in, ['foo', 'bar']] + ]) + }) + + it('parses array rules with expression arguments', () => { + expect(parseRules([ + ['matches', /^abc/, '1234'] + ], rules)).toEqual([ + [rules.matches, [/^abc/, '1234']] + ]) + }) +}) diff --git a/themes/snow/_inputs.scss b/themes/snow/_inputs.scss index de5acd9..1dfae89 100644 --- a/themes/snow/_inputs.scss +++ b/themes/snow/_inputs.scss @@ -25,6 +25,20 @@ margin-bottom: .25em; } + .formulate-input-errors { + list-style-type: none; + padding: 0; + margin: 0; + } + + .formulate-input-error { + color: $formulate-error; + font-size: .8em; + font-weight: 300; + line-height: 1.5; + margin-bottom: .25em; + } + .formulate-input-group-item { margin-bottom: .5em; } diff --git a/themes/snow/_variables.scss b/themes/snow/_variables.scss index e55c537..4b4ad35 100644 --- a/themes/snow/_variables.scss +++ b/themes/snow/_variables.scss @@ -15,6 +15,8 @@ $formulate-blue-l: #f3f4f4; $formulate-green: #41b883; +$formulate-error: #960505; + $formulate-yellow-d: #6b5900; $formulate-yellow: #e6c000; $formulate-yellow-l: #fff8d2;