1
0
mirror of synced 2024-11-25 23:06:02 +03:00

Adds new date format capability and associated tests

This commit is contained in:
Justin Schroeder 2019-11-14 01:00:56 -05:00
parent ba24fc21e8
commit cb9349a6cf
21 changed files with 981 additions and 104 deletions

273
dist/formulate.esm.js vendored
View File

@ -36,10 +36,6 @@ var library = {
classification: 'text',
component: 'FormulateInputText'
},
range: {
classification: 'text',
component: 'FormulateInputText'
},
search: {
classification: 'text',
component: 'FormulateInputText'
@ -65,6 +61,12 @@ var library = {
component: 'FormulateInputText'
},
// === SLIDER INPUTS
range: {
classification: 'slider',
component: 'FormulateInputSlider'
},
// === MULTI LINE TEXT INPUTS
textarea: {
classification: 'textarea',
@ -218,6 +220,33 @@ function parseRule (rule, rules) {
return false
}
/**
* Escape a string for use in regular expressions.
* @param {string} string
*/
function escapeRegExp (string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // $& means the whole matched string
}
/**
* Given a string format (date) return a regex to match against.
* @param {string} format
*/
function regexForFormat (format) {
var escaped = "^" + (escapeRegExp(format)) + "$";
var formats = {
MM: '(0[1-9]|1[012])',
M: '([1-9]|1[012])',
DD: '([012][1-9]|3[01])',
D: '([012]?[1-9]|3[01])',
YYYY: '\\d{4}',
YY: '\\d{2}'
};
return new RegExp(Object.keys(formats).reduce(function (regex, format) {
return regex.replace(format, formats[format])
}, escaped))
}
/**
* Library of rules
*/
@ -304,10 +333,18 @@ var rules = {
},
/**
* Rule: ensures the value is a date according to Date.parse()
* Rule: ensures the value is a date according to Date.parse(), or a format
* regex.
*/
date: function (value) {
return Promise.resolve(!isNaN(Date.parse(value)))
date: function (value, format) {
if ( format === void 0 ) format = false;
return Promise.resolve((function () {
if (format && typeof format === 'string') {
return regexForFormat(format).test(value)
}
return !isNaN(Date.parse(value))
})())
},
/**
@ -542,7 +579,11 @@ var en = {
*/
date: function (ref) {
var name = ref.name;
var args = ref.args;
if (Array.isArray(args) && args.length) {
return ((sentence(name)) + " is not a valid, please use the format " + (args[0]))
}
return ((sentence(name)) + " is not a valid date.")
},
@ -562,7 +603,10 @@ var en = {
var name = ref.name;
var value = ref.value;
return (value + " is not a valid email address.")
if (!value) {
return 'Please enter a valid email address.'
}
return ("“" + value + "” is not a valid email address.")
},
/**
@ -572,10 +616,10 @@ var en = {
var name = ref.name;
var value = ref.value;
if (typeof value === 'string') {
if (typeof value === 'string' && value) {
return ("“" + (sentence(value)) + "” is not an allowed " + name + ".")
}
return ((sentence(name)) + " is not an allowed value.")
return ("This is not an allowed " + name + ".")
},
/**
@ -1620,7 +1664,8 @@ var __vue_render__$4 = function() {
return _c(
"div",
{
staticClass: "formulate-input-element formulate-input-element--box",
class:
"formulate-input-element formulate-input-element--" + _vm.context.type,
attrs: { "data-type": _vm.context.type }
},
[
@ -1795,7 +1840,9 @@ var __vue_render__$5 = function() {
return _c(
"div",
{
staticClass: "formulate-input-element formulate-input-element--textarea"
class:
"formulate-input-element formulate-input-element--" + _vm.context.type,
attrs: { "data-type": _vm.context.type }
},
[
_vm.type === "checkbox"
@ -1967,7 +2014,11 @@ var __vue_render__$6 = function() {
var _c = _vm._self._c || _h;
return _c(
"div",
{ staticClass: "formulate-input-element formulate-input-element--select" },
{
class:
"formulate-input-element formulate-input-element--" + _vm.context.type,
attrs: { "data-type": _vm.context.type }
},
[
_c(
"select",
@ -2104,7 +2155,7 @@ __vue_render__$6._withStripped = true;
//
var script$7 = {
name: 'FormulateInputTextArea',
name: 'FormulateInputText',
mixins: [FormulateInputMixin]
};
@ -2119,7 +2170,172 @@ var __vue_render__$7 = function() {
return _c(
"div",
{
staticClass: "formulate-input-element formulate-input-element--textarea"
class:
"formulate-input-element formulate-input-element--" + _vm.context.type,
attrs: { "data-type": _vm.context.type }
},
[
_vm.type === "checkbox"
? _c(
"input",
_vm._b(
{
directives: [
{
name: "model",
rawName: "v-model",
value: _vm.context.model,
expression: "context.model"
}
],
attrs: { type: "checkbox" },
domProps: {
checked: Array.isArray(_vm.context.model)
? _vm._i(_vm.context.model, null) > -1
: _vm.context.model
},
on: {
blur: _vm.context.blurHandler,
change: function($event) {
var $$a = _vm.context.model,
$$el = $event.target,
$$c = $$el.checked ? true : false;
if (Array.isArray($$a)) {
var $$v = null,
$$i = _vm._i($$a, $$v);
if ($$el.checked) {
$$i < 0 &&
_vm.$set(_vm.context, "model", $$a.concat([$$v]));
} else {
$$i > -1 &&
_vm.$set(
_vm.context,
"model",
$$a.slice(0, $$i).concat($$a.slice($$i + 1))
);
}
} else {
_vm.$set(_vm.context, "model", $$c);
}
}
}
},
"input",
_vm.attributes,
false
)
)
: _vm.type === "radio"
? _c(
"input",
_vm._b(
{
directives: [
{
name: "model",
rawName: "v-model",
value: _vm.context.model,
expression: "context.model"
}
],
attrs: { type: "radio" },
domProps: { checked: _vm._q(_vm.context.model, null) },
on: {
blur: _vm.context.blurHandler,
change: function($event) {
return _vm.$set(_vm.context, "model", null)
}
}
},
"input",
_vm.attributes,
false
)
)
: _c(
"input",
_vm._b(
{
directives: [
{
name: "model",
rawName: "v-model",
value: _vm.context.model,
expression: "context.model"
}
],
attrs: { type: _vm.type },
domProps: { value: _vm.context.model },
on: {
blur: _vm.context.blurHandler,
input: function($event) {
if ($event.target.composing) {
return
}
_vm.$set(_vm.context, "model", $event.target.value);
}
}
},
"input",
_vm.attributes,
false
)
)
]
)
};
var __vue_staticRenderFns__$7 = [];
__vue_render__$7._withStripped = true;
/* style */
var __vue_inject_styles__$7 = undefined;
/* scoped */
var __vue_scope_id__$7 = undefined;
/* module identifier */
var __vue_module_identifier__$7 = undefined;
/* functional template */
var __vue_is_functional_template__$7 = false;
/* style inject */
/* style inject SSR */
/* style inject shadow dom */
var FormulateInputSlider = normalizeComponent(
{ 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,
undefined
);
//
var script$8 = {
name: 'FormulateInputTextArea',
mixins: [FormulateInputMixin]
};
/* script */
var __vue_script__$8 = script$8;
/* template */
var __vue_render__$8 = function() {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
return _c(
"div",
{
staticClass: "formulate-input-element formulate-input-element--textarea",
attrs: { "data-type": "textarea" }
},
[
_c(
@ -2153,17 +2369,17 @@ var __vue_render__$7 = function() {
]
)
};
var __vue_staticRenderFns__$7 = [];
__vue_render__$7._withStripped = true;
var __vue_staticRenderFns__$8 = [];
__vue_render__$8._withStripped = true;
/* style */
var __vue_inject_styles__$7 = undefined;
var __vue_inject_styles__$8 = undefined;
/* scoped */
var __vue_scope_id__$7 = undefined;
var __vue_scope_id__$8 = undefined;
/* module identifier */
var __vue_module_identifier__$7 = undefined;
var __vue_module_identifier__$8 = undefined;
/* functional template */
var __vue_is_functional_template__$7 = false;
var __vue_is_functional_template__$8 = false;
/* style inject */
/* style inject SSR */
@ -2173,12 +2389,12 @@ __vue_render__$7._withStripped = true;
var FormulateInputTextArea = normalizeComponent(
{ 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,
{ render: __vue_render__$8, staticRenderFns: __vue_staticRenderFns__$8 },
__vue_inject_styles__$8,
__vue_script__$8,
__vue_scope_id__$8,
__vue_is_functional_template__$8,
__vue_module_identifier__$8,
false,
undefined,
undefined,
@ -2198,6 +2414,7 @@ var Formulate = function Formulate () {
FormulateInputText: FormulateInputText,
FormulateInputGroup: FormulateInputGroup,
FormulateInputSelect: FormulateInputSelect,
FormulateInputSlider: FormulateInputSlider,
FormulateInputTextArea: FormulateInputTextArea
},
library: library,
@ -2282,6 +2499,8 @@ Formulate.prototype.validationMessage = function validationMessage (rule, valida
var generators = this.options.locales[this.options.locale];
if (generators.hasOwnProperty(rule)) {
return generators[rule](validationContext)
} else if (rule[0] === '_' && generators.hasOwnProperty(rule.substr(1))) {
return generators[rule.substr(1)](validationContext)
}
if (generators.hasOwnProperty('default')) {
return generators.default(validationContext)

273
dist/formulate.min.js vendored
View File

@ -39,10 +39,6 @@ var Formulate = (function (exports, isUrl, isPlainObject, nanoid) {
classification: 'text',
component: 'FormulateInputText'
},
range: {
classification: 'text',
component: 'FormulateInputText'
},
search: {
classification: 'text',
component: 'FormulateInputText'
@ -68,6 +64,12 @@ var Formulate = (function (exports, isUrl, isPlainObject, nanoid) {
component: 'FormulateInputText'
},
// === SLIDER INPUTS
range: {
classification: 'slider',
component: 'FormulateInputSlider'
},
// === MULTI LINE TEXT INPUTS
textarea: {
classification: 'textarea',
@ -221,6 +223,33 @@ var Formulate = (function (exports, isUrl, isPlainObject, nanoid) {
return false
}
/**
* Escape a string for use in regular expressions.
* @param {string} string
*/
function escapeRegExp (string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // $& means the whole matched string
}
/**
* Given a string format (date) return a regex to match against.
* @param {string} format
*/
function regexForFormat (format) {
var escaped = "^" + (escapeRegExp(format)) + "$";
var formats = {
MM: '(0[1-9]|1[012])',
M: '([1-9]|1[012])',
DD: '([012][1-9]|3[01])',
D: '([012]?[1-9]|3[01])',
YYYY: '\\d{4}',
YY: '\\d{2}'
};
return new RegExp(Object.keys(formats).reduce(function (regex, format) {
return regex.replace(format, formats[format])
}, escaped))
}
/**
* Library of rules
*/
@ -307,10 +336,18 @@ var Formulate = (function (exports, isUrl, isPlainObject, nanoid) {
},
/**
* Rule: ensures the value is a date according to Date.parse()
* Rule: ensures the value is a date according to Date.parse(), or a format
* regex.
*/
date: function (value) {
return Promise.resolve(!isNaN(Date.parse(value)))
date: function (value, format) {
if ( format === void 0 ) format = false;
return Promise.resolve((function () {
if (format && typeof format === 'string') {
return regexForFormat(format).test(value)
}
return !isNaN(Date.parse(value))
})())
},
/**
@ -545,7 +582,11 @@ var Formulate = (function (exports, isUrl, isPlainObject, nanoid) {
*/
date: function (ref) {
var name = ref.name;
var args = ref.args;
if (Array.isArray(args) && args.length) {
return ((sentence(name)) + " is not a valid, please use the format " + (args[0]))
}
return ((sentence(name)) + " is not a valid date.")
},
@ -565,7 +606,10 @@ var Formulate = (function (exports, isUrl, isPlainObject, nanoid) {
var name = ref.name;
var value = ref.value;
return (value + " is not a valid email address.")
if (!value) {
return 'Please enter a valid email address.'
}
return ("“" + value + "” is not a valid email address.")
},
/**
@ -575,10 +619,10 @@ var Formulate = (function (exports, isUrl, isPlainObject, nanoid) {
var name = ref.name;
var value = ref.value;
if (typeof value === 'string') {
if (typeof value === 'string' && value) {
return ("“" + (sentence(value)) + "” is not an allowed " + name + ".")
}
return ((sentence(name)) + " is not an allowed value.")
return ("This is not an allowed " + name + ".")
},
/**
@ -1623,7 +1667,8 @@ var Formulate = (function (exports, isUrl, isPlainObject, nanoid) {
return _c(
"div",
{
staticClass: "formulate-input-element formulate-input-element--box",
class:
"formulate-input-element formulate-input-element--" + _vm.context.type,
attrs: { "data-type": _vm.context.type }
},
[
@ -1798,7 +1843,9 @@ var Formulate = (function (exports, isUrl, isPlainObject, nanoid) {
return _c(
"div",
{
staticClass: "formulate-input-element formulate-input-element--textarea"
class:
"formulate-input-element formulate-input-element--" + _vm.context.type,
attrs: { "data-type": _vm.context.type }
},
[
_vm.type === "checkbox"
@ -1970,7 +2017,11 @@ var Formulate = (function (exports, isUrl, isPlainObject, nanoid) {
var _c = _vm._self._c || _h;
return _c(
"div",
{ staticClass: "formulate-input-element formulate-input-element--select" },
{
class:
"formulate-input-element formulate-input-element--" + _vm.context.type,
attrs: { "data-type": _vm.context.type }
},
[
_c(
"select",
@ -2107,7 +2158,7 @@ var Formulate = (function (exports, isUrl, isPlainObject, nanoid) {
//
var script$7 = {
name: 'FormulateInputTextArea',
name: 'FormulateInputText',
mixins: [FormulateInputMixin]
};
@ -2122,7 +2173,172 @@ var Formulate = (function (exports, isUrl, isPlainObject, nanoid) {
return _c(
"div",
{
staticClass: "formulate-input-element formulate-input-element--textarea"
class:
"formulate-input-element formulate-input-element--" + _vm.context.type,
attrs: { "data-type": _vm.context.type }
},
[
_vm.type === "checkbox"
? _c(
"input",
_vm._b(
{
directives: [
{
name: "model",
rawName: "v-model",
value: _vm.context.model,
expression: "context.model"
}
],
attrs: { type: "checkbox" },
domProps: {
checked: Array.isArray(_vm.context.model)
? _vm._i(_vm.context.model, null) > -1
: _vm.context.model
},
on: {
blur: _vm.context.blurHandler,
change: function($event) {
var $$a = _vm.context.model,
$$el = $event.target,
$$c = $$el.checked ? true : false;
if (Array.isArray($$a)) {
var $$v = null,
$$i = _vm._i($$a, $$v);
if ($$el.checked) {
$$i < 0 &&
_vm.$set(_vm.context, "model", $$a.concat([$$v]));
} else {
$$i > -1 &&
_vm.$set(
_vm.context,
"model",
$$a.slice(0, $$i).concat($$a.slice($$i + 1))
);
}
} else {
_vm.$set(_vm.context, "model", $$c);
}
}
}
},
"input",
_vm.attributes,
false
)
)
: _vm.type === "radio"
? _c(
"input",
_vm._b(
{
directives: [
{
name: "model",
rawName: "v-model",
value: _vm.context.model,
expression: "context.model"
}
],
attrs: { type: "radio" },
domProps: { checked: _vm._q(_vm.context.model, null) },
on: {
blur: _vm.context.blurHandler,
change: function($event) {
return _vm.$set(_vm.context, "model", null)
}
}
},
"input",
_vm.attributes,
false
)
)
: _c(
"input",
_vm._b(
{
directives: [
{
name: "model",
rawName: "v-model",
value: _vm.context.model,
expression: "context.model"
}
],
attrs: { type: _vm.type },
domProps: { value: _vm.context.model },
on: {
blur: _vm.context.blurHandler,
input: function($event) {
if ($event.target.composing) {
return
}
_vm.$set(_vm.context, "model", $event.target.value);
}
}
},
"input",
_vm.attributes,
false
)
)
]
)
};
var __vue_staticRenderFns__$7 = [];
__vue_render__$7._withStripped = true;
/* style */
var __vue_inject_styles__$7 = undefined;
/* scoped */
var __vue_scope_id__$7 = undefined;
/* module identifier */
var __vue_module_identifier__$7 = undefined;
/* functional template */
var __vue_is_functional_template__$7 = false;
/* style inject */
/* style inject SSR */
/* style inject shadow dom */
var FormulateInputSlider = normalizeComponent(
{ 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,
undefined
);
//
var script$8 = {
name: 'FormulateInputTextArea',
mixins: [FormulateInputMixin]
};
/* script */
var __vue_script__$8 = script$8;
/* template */
var __vue_render__$8 = function() {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
return _c(
"div",
{
staticClass: "formulate-input-element formulate-input-element--textarea",
attrs: { "data-type": "textarea" }
},
[
_c(
@ -2156,17 +2372,17 @@ var Formulate = (function (exports, isUrl, isPlainObject, nanoid) {
]
)
};
var __vue_staticRenderFns__$7 = [];
__vue_render__$7._withStripped = true;
var __vue_staticRenderFns__$8 = [];
__vue_render__$8._withStripped = true;
/* style */
var __vue_inject_styles__$7 = undefined;
var __vue_inject_styles__$8 = undefined;
/* scoped */
var __vue_scope_id__$7 = undefined;
var __vue_scope_id__$8 = undefined;
/* module identifier */
var __vue_module_identifier__$7 = undefined;
var __vue_module_identifier__$8 = undefined;
/* functional template */
var __vue_is_functional_template__$7 = false;
var __vue_is_functional_template__$8 = false;
/* style inject */
/* style inject SSR */
@ -2176,12 +2392,12 @@ var Formulate = (function (exports, isUrl, isPlainObject, nanoid) {
var FormulateInputTextArea = normalizeComponent(
{ 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,
{ render: __vue_render__$8, staticRenderFns: __vue_staticRenderFns__$8 },
__vue_inject_styles__$8,
__vue_script__$8,
__vue_scope_id__$8,
__vue_is_functional_template__$8,
__vue_module_identifier__$8,
false,
undefined,
undefined,
@ -2201,6 +2417,7 @@ var Formulate = (function (exports, isUrl, isPlainObject, nanoid) {
FormulateInputText: FormulateInputText,
FormulateInputGroup: FormulateInputGroup,
FormulateInputSelect: FormulateInputSelect,
FormulateInputSlider: FormulateInputSlider,
FormulateInputTextArea: FormulateInputTextArea
},
library: library,
@ -2285,6 +2502,8 @@ var Formulate = (function (exports, isUrl, isPlainObject, nanoid) {
var generators = this.options.locales[this.options.locale];
if (generators.hasOwnProperty(rule)) {
return generators[rule](validationContext)
} else if (rule[0] === '_' && generators.hasOwnProperty(rule.substr(1))) {
return generators[rule.substr(1)](validationContext)
}
if (generators.hasOwnProperty('default')) {
return generators.default(validationContext)

273
dist/formulate.umd.js vendored
View File

@ -42,10 +42,6 @@
classification: 'text',
component: 'FormulateInputText'
},
range: {
classification: 'text',
component: 'FormulateInputText'
},
search: {
classification: 'text',
component: 'FormulateInputText'
@ -71,6 +67,12 @@
component: 'FormulateInputText'
},
// === SLIDER INPUTS
range: {
classification: 'slider',
component: 'FormulateInputSlider'
},
// === MULTI LINE TEXT INPUTS
textarea: {
classification: 'textarea',
@ -224,6 +226,33 @@
return false
}
/**
* Escape a string for use in regular expressions.
* @param {string} string
*/
function escapeRegExp (string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // $& means the whole matched string
}
/**
* Given a string format (date) return a regex to match against.
* @param {string} format
*/
function regexForFormat (format) {
var escaped = "^" + (escapeRegExp(format)) + "$";
var formats = {
MM: '(0[1-9]|1[012])',
M: '([1-9]|1[012])',
DD: '([012][1-9]|3[01])',
D: '([012]?[1-9]|3[01])',
YYYY: '\\d{4}',
YY: '\\d{2}'
};
return new RegExp(Object.keys(formats).reduce(function (regex, format) {
return regex.replace(format, formats[format])
}, escaped))
}
/**
* Library of rules
*/
@ -310,10 +339,18 @@
},
/**
* Rule: ensures the value is a date according to Date.parse()
* Rule: ensures the value is a date according to Date.parse(), or a format
* regex.
*/
date: function (value) {
return Promise.resolve(!isNaN(Date.parse(value)))
date: function (value, format) {
if ( format === void 0 ) format = false;
return Promise.resolve((function () {
if (format && typeof format === 'string') {
return regexForFormat(format).test(value)
}
return !isNaN(Date.parse(value))
})())
},
/**
@ -548,7 +585,11 @@
*/
date: function (ref) {
var name = ref.name;
var args = ref.args;
if (Array.isArray(args) && args.length) {
return ((sentence(name)) + " is not a valid, please use the format " + (args[0]))
}
return ((sentence(name)) + " is not a valid date.")
},
@ -568,7 +609,10 @@
var name = ref.name;
var value = ref.value;
return (value + " is not a valid email address.")
if (!value) {
return 'Please enter a valid email address.'
}
return ("“" + value + "” is not a valid email address.")
},
/**
@ -578,10 +622,10 @@
var name = ref.name;
var value = ref.value;
if (typeof value === 'string') {
if (typeof value === 'string' && value) {
return ("“" + (sentence(value)) + "” is not an allowed " + name + ".")
}
return ((sentence(name)) + " is not an allowed value.")
return ("This is not an allowed " + name + ".")
},
/**
@ -1626,7 +1670,8 @@
return _c(
"div",
{
staticClass: "formulate-input-element formulate-input-element--box",
class:
"formulate-input-element formulate-input-element--" + _vm.context.type,
attrs: { "data-type": _vm.context.type }
},
[
@ -1801,7 +1846,9 @@
return _c(
"div",
{
staticClass: "formulate-input-element formulate-input-element--textarea"
class:
"formulate-input-element formulate-input-element--" + _vm.context.type,
attrs: { "data-type": _vm.context.type }
},
[
_vm.type === "checkbox"
@ -1973,7 +2020,11 @@
var _c = _vm._self._c || _h;
return _c(
"div",
{ staticClass: "formulate-input-element formulate-input-element--select" },
{
class:
"formulate-input-element formulate-input-element--" + _vm.context.type,
attrs: { "data-type": _vm.context.type }
},
[
_c(
"select",
@ -2110,7 +2161,7 @@
//
var script$7 = {
name: 'FormulateInputTextArea',
name: 'FormulateInputText',
mixins: [FormulateInputMixin]
};
@ -2125,7 +2176,172 @@
return _c(
"div",
{
staticClass: "formulate-input-element formulate-input-element--textarea"
class:
"formulate-input-element formulate-input-element--" + _vm.context.type,
attrs: { "data-type": _vm.context.type }
},
[
_vm.type === "checkbox"
? _c(
"input",
_vm._b(
{
directives: [
{
name: "model",
rawName: "v-model",
value: _vm.context.model,
expression: "context.model"
}
],
attrs: { type: "checkbox" },
domProps: {
checked: Array.isArray(_vm.context.model)
? _vm._i(_vm.context.model, null) > -1
: _vm.context.model
},
on: {
blur: _vm.context.blurHandler,
change: function($event) {
var $$a = _vm.context.model,
$$el = $event.target,
$$c = $$el.checked ? true : false;
if (Array.isArray($$a)) {
var $$v = null,
$$i = _vm._i($$a, $$v);
if ($$el.checked) {
$$i < 0 &&
_vm.$set(_vm.context, "model", $$a.concat([$$v]));
} else {
$$i > -1 &&
_vm.$set(
_vm.context,
"model",
$$a.slice(0, $$i).concat($$a.slice($$i + 1))
);
}
} else {
_vm.$set(_vm.context, "model", $$c);
}
}
}
},
"input",
_vm.attributes,
false
)
)
: _vm.type === "radio"
? _c(
"input",
_vm._b(
{
directives: [
{
name: "model",
rawName: "v-model",
value: _vm.context.model,
expression: "context.model"
}
],
attrs: { type: "radio" },
domProps: { checked: _vm._q(_vm.context.model, null) },
on: {
blur: _vm.context.blurHandler,
change: function($event) {
return _vm.$set(_vm.context, "model", null)
}
}
},
"input",
_vm.attributes,
false
)
)
: _c(
"input",
_vm._b(
{
directives: [
{
name: "model",
rawName: "v-model",
value: _vm.context.model,
expression: "context.model"
}
],
attrs: { type: _vm.type },
domProps: { value: _vm.context.model },
on: {
blur: _vm.context.blurHandler,
input: function($event) {
if ($event.target.composing) {
return
}
_vm.$set(_vm.context, "model", $event.target.value);
}
}
},
"input",
_vm.attributes,
false
)
)
]
)
};
var __vue_staticRenderFns__$7 = [];
__vue_render__$7._withStripped = true;
/* style */
var __vue_inject_styles__$7 = undefined;
/* scoped */
var __vue_scope_id__$7 = undefined;
/* module identifier */
var __vue_module_identifier__$7 = undefined;
/* functional template */
var __vue_is_functional_template__$7 = false;
/* style inject */
/* style inject SSR */
/* style inject shadow dom */
var FormulateInputSlider = normalizeComponent(
{ 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,
undefined
);
//
var script$8 = {
name: 'FormulateInputTextArea',
mixins: [FormulateInputMixin]
};
/* script */
var __vue_script__$8 = script$8;
/* template */
var __vue_render__$8 = function() {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
return _c(
"div",
{
staticClass: "formulate-input-element formulate-input-element--textarea",
attrs: { "data-type": "textarea" }
},
[
_c(
@ -2159,17 +2375,17 @@
]
)
};
var __vue_staticRenderFns__$7 = [];
__vue_render__$7._withStripped = true;
var __vue_staticRenderFns__$8 = [];
__vue_render__$8._withStripped = true;
/* style */
var __vue_inject_styles__$7 = undefined;
var __vue_inject_styles__$8 = undefined;
/* scoped */
var __vue_scope_id__$7 = undefined;
var __vue_scope_id__$8 = undefined;
/* module identifier */
var __vue_module_identifier__$7 = undefined;
var __vue_module_identifier__$8 = undefined;
/* functional template */
var __vue_is_functional_template__$7 = false;
var __vue_is_functional_template__$8 = false;
/* style inject */
/* style inject SSR */
@ -2179,12 +2395,12 @@
var FormulateInputTextArea = normalizeComponent(
{ 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,
{ render: __vue_render__$8, staticRenderFns: __vue_staticRenderFns__$8 },
__vue_inject_styles__$8,
__vue_script__$8,
__vue_scope_id__$8,
__vue_is_functional_template__$8,
__vue_module_identifier__$8,
false,
undefined,
undefined,
@ -2204,6 +2420,7 @@
FormulateInputText: FormulateInputText,
FormulateInputGroup: FormulateInputGroup,
FormulateInputSelect: FormulateInputSelect,
FormulateInputSlider: FormulateInputSlider,
FormulateInputTextArea: FormulateInputTextArea
},
library: library,
@ -2288,6 +2505,8 @@
var generators = this.options.locales[this.options.locale];
if (generators.hasOwnProperty(rule)) {
return generators[rule](validationContext)
} else if (rule[0] === '_' && generators.hasOwnProperty(rule.substr(1))) {
return generators[rule.substr(1)](validationContext)
}
if (generators.hasOwnProperty('default')) {
return generators.default(validationContext)

39
dist/snow.css vendored
View File

@ -46,6 +46,45 @@
.formulate-input[data-classification='text'] input:focus {
outline: 0;
border: 1px solid #41b883; }
.formulate-input[data-classification='slider'] input {
appearance: none;
width: 100%;
font-size: 1em;
padding: .5em 0; }
.formulate-input[data-classification='slider'] input:focus {
outline: 0; }
.formulate-input[data-classification='slider'] input::-webkit-slider-thumb {
cursor: pointer;
appearance: none;
width: 1em;
height: 1em;
border-radius: 1em;
background-color: #41b883;
margin-top: calc(-.5em + 2px); }
.formulate-input[data-classification='slider'] input::-moz-range-thumb {
cursor: pointer;
appearance: none;
width: 1em;
height: 1em;
border-radius: 1em;
background-color: #41b883;
margin-top: calc(-.5em + 2px); }
.formulate-input[data-classification='slider'] input::-ms-thumb {
cursor: pointer;
appearance: none;
width: 1em;
height: 1em;
border-radius: 1em;
background-color: #41b883;
margin-top: calc(-.5em + 2px); }
.formulate-input[data-classification='slider'] input::-webkit-slider-runnable-track {
appearance: none;
width: 100%;
height: 4px;
background-color: #efefef;
border-radius: 3px;
margin: 0;
padding: 0; }
.formulate-input[data-classification='textarea'] textarea {
appearance: none;
border-radius: .3em;

4
dist/snow.min.css vendored

File diff suppressed because one or more lines are too long

View File

@ -9,6 +9,7 @@ import FormulateInputGroup from './FormulateInputGroup.vue'
import FormulateInputBox from './inputs/FormulateInputBox.vue'
import FormulateInputText from './inputs/FormulateInputText.vue'
import FormulateInputSelect from './inputs/FormulateInputSelect.vue'
import FormulateInputSlider from './inputs/FormulateInputSlider.vue'
import FormulateInputTextArea from './inputs/FormulateInputTextArea.vue'
/**
@ -28,6 +29,7 @@ class Formulate {
FormulateInputText,
FormulateInputGroup,
FormulateInputSelect,
FormulateInputSlider,
FormulateInputTextArea
},
library,
@ -112,6 +114,8 @@ class Formulate {
const generators = this.options.locales[this.options.locale]
if (generators.hasOwnProperty(rule)) {
return generators[rule](validationContext)
} else if (rule[0] === '_' && generators.hasOwnProperty(rule.substr(1))) {
return generators[rule.substr(1)](validationContext)
}
if (generators.hasOwnProperty('default')) {
return generators.default(validationContext)

View File

@ -1,6 +1,6 @@
<template>
<div
class="formulate-input-element formulate-input-element--box"
:class="`formulate-input-element formulate-input-element--${context.type}`"
:data-type="context.type"
>
<input

View File

@ -1,6 +1,7 @@
<template>
<div
class="formulate-input-element formulate-input-element--select"
:class="`formulate-input-element formulate-input-element--${context.type}`"
:data-type="context.type"
>
<select
v-model="context.model"

View File

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

View File

@ -1,6 +1,7 @@
<template>
<div
class="formulate-input-element formulate-input-element--textarea"
:class="`formulate-input-element formulate-input-element--${context.type}`"
:data-type="context.type"
>
<input
v-model="context.model"

View File

@ -1,6 +1,7 @@
<template>
<div
class="formulate-input-element formulate-input-element--textarea"
data-type="textarea"
>
<textarea
v-model="context.model"

View File

@ -32,10 +32,6 @@ export default {
classification: 'text',
component: 'FormulateInputText'
},
range: {
classification: 'text',
component: 'FormulateInputText'
},
search: {
classification: 'text',
component: 'FormulateInputText'
@ -61,6 +57,12 @@ export default {
component: 'FormulateInputText'
},
// === SLIDER INPUTS
range: {
classification: 'slider',
component: 'FormulateInputSlider'
},
// === MULTI LINE TEXT INPUTS
textarea: {
classification: 'textarea',

View File

@ -1,5 +1,5 @@
import isUrl from 'is-url'
import { shallowEqualObjects } from './utils'
import { shallowEqualObjects, regexForFormat } from './utils'
/**
* Library of rules
@ -76,10 +76,16 @@ export default {
},
/**
* Rule: ensures the value is a date according to Date.parse()
* Rule: ensures the value is a date according to Date.parse(), or a format
* regex.
*/
date: function (value) {
return Promise.resolve(!isNaN(Date.parse(value)))
date: function (value, format = false) {
return Promise.resolve((() => {
if (format && typeof format === 'string') {
return regexForFormat(format).test(value)
}
return !isNaN(Date.parse(value))
})())
},
/**

View File

@ -145,3 +145,30 @@ function parseRule (rule, rules) {
}
return false
}
/**
* Escape a string for use in regular expressions.
* @param {string} string
*/
export function escapeRegExp (string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // $& means the whole matched string
}
/**
* Given a string format (date) return a regex to match against.
* @param {string} format
*/
export function regexForFormat (format) {
let escaped = `^${escapeRegExp(format)}$`
const formats = {
MM: '(0[1-9]|1[012])',
M: '([1-9]|1[012])',
DD: '([012][1-9]|3[01])',
D: '([012]?[1-9]|3[01])',
YYYY: '\\d{4}',
YY: '\\d{2}'
}
return new RegExp(Object.keys(formats).reduce((regex, format) => {
return regex.replace(format, formats[format])
}, escaped))
}

View File

@ -58,7 +58,10 @@ export default {
/**
* Is not a valid date.
*/
date: function ({ name }) {
date: function ({ name, args }) {
if (Array.isArray(args) && args.length) {
return `${s(name)} is not a valid, please use the format ${args[0]}`
}
return `${s(name)} is not a valid date.`
},
@ -73,17 +76,20 @@ export default {
* Is not a valid email address.
*/
email: function ({ name, value }) {
return `${value} is not a valid email address.`
if (!value) {
return 'Please enter a valid email address.'
}
return `${value}” is not a valid email address.`
},
/**
* Value is an allowed value.
*/
in: function ({ name, value }) {
if (typeof value === 'string') {
if (typeof value === 'string' && value) {
return `${s(value)}” is not an allowed ${name}.`
}
return `${s(name)} is not an allowed value.`
return `This is not an allowed ${name}.`
},
/**

View File

@ -55,6 +55,7 @@ test('installs on vue instance', () => {
'FormulateInputText',
'FormulateInputGroup',
'FormulateInputSelect',
'FormulateInputSlider',
'FormulateInputTextArea'
]
const registry = []

View File

@ -0,0 +1,18 @@
import Vue from 'vue'
import { mount } from '@vue/test-utils'
import Formulate from '../src/Formulate.js'
import FormulateInput from '../src/FormulateInput.vue'
import FormulateInputSlider from '../src/inputs/FormulateInputSlider.vue'
Vue.use(Formulate)
/**
* Test each type of slider element
*/
describe('FormulateInputSlider', () => {
it('renders range input when type is "range"', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'range' } })
expect(wrapper.contains(FormulateInputSlider)).toBe(true)
})
})

View File

@ -52,11 +52,6 @@ describe('FormulateInputText', () => {
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)

View File

@ -128,11 +128,16 @@ describe('date', () => {
it('passes with only month', async () => expect(await rules.date('January')).toBe(false))
it('passes with valid date format', async () => expect(await rules.date('12/17/1987', 'MM/DD/YYYY')).toBe(true))
it('fails with simple number and date format', async () => expect(await rules.date('1234', 'MM/DD/YYYY')).toBe(false))
it('fails with only day of week', async () => expect(await rules.date('saturday')).toBe(false))
it('fails with random string', async () => expect(await rules.date('Pepsi 17')).toBe(false))
it('fails with random number', async () => expect(await rules.date('1872301237')).toBe(false))
})

View File

@ -1,4 +1,4 @@
import { parseRules } from '@/libs/utils'
import { parseRules, regexForFormat } from '@/libs/utils'
import rules from '@/libs/rules'
describe('parseRules', () => {
@ -42,3 +42,44 @@ describe('parseRules', () => {
])
})
})
describe('regexForFormat', () => {
it('allows MM format with other characters', () => expect(regexForFormat('abc/MM').test('abc/01')).toBe(true))
it('fails MM format with single digit', () => expect(regexForFormat('abc/MM').test('abc/1')).toBe(false))
it('allows M format with single digit', () => expect(regexForFormat('M/abc').test('1/abc')).toBe(true))
it('fails MM format when out of range', () => expect(regexForFormat('M/abc').test('13/abc')).toBe(false))
it('fails M format when out of range', () => expect(regexForFormat('M/abc').test('55/abc')).toBe(false))
it('Replaces double digits before singles', () => expect(regexForFormat('MMM').test('313131')).toBe(false))
it('allows DD format with zero digit', () => expect(regexForFormat('xyz/DD').test('xyz/01')).toBe(true))
it('fails DD format with single digit', () => expect(regexForFormat('xyz/DD').test('xyz/9')).toBe(false))
it('allows D format with single digit', () => expect(regexForFormat('xyz/D').test('xyz/9')).toBe(true))
it('fails D format with out of range digit', () => expect(regexForFormat('xyz/D').test('xyz/92')).toBe(false))
it('fails DD format with out of range digit', () => expect(regexForFormat('xyz/D').test('xyz/32')).toBe(false))
it('allows YY format with double zeros', () => expect(regexForFormat('YY').test('00')).toBe(true))
it('fails YY format with four zeros', () => expect(regexForFormat('YY').test('0000')).toBe(false))
it('allows YYYY format with four zeros', () => expect(regexForFormat('YYYY').test('0000')).toBe(true))
it('allows MD-YY', () => expect(regexForFormat('MD-YY').test('12-00')).toBe(true))
it('allows DM-YY', () => expect(regexForFormat('DM-YY').test('12-00')).toBe(true))
it('allows date like MM/DD/YYYY', () => expect(regexForFormat('MM/DD/YYYY').test('12/18/1987')).toBe(true))
it('allows date like YYYY-MM-DD', () => expect(regexForFormat('YYYY-MM-DD').test('1987-01-31')).toBe(true))
it('fails date like YYYY-MM-DD with out of bounds day', () => expect(regexForFormat('YYYY-MM-DD').test('1987-01-32')).toBe(false))
})

View File

@ -57,6 +57,57 @@
}
}
// Slider inputs
// -----------------------------------------------------------------------------
&[data-classification='slider'] {
input {
appearance: none;
width: 100%;
font-size: 1em;
padding: .5em 0;
&:focus {
outline: 0;
}
@mixin thumb {
cursor: pointer;
appearance: none;
width: 1em;
height: 1em;
border-radius: 1em;
background-color: $formulate-green;
margin-top: calc(-.5em + 2px);
}
@mixin track {
appearance: none;
width: 100%;
height: 4px;
background-color: $formulate-gray;
border-radius: 3px;
margin: 0;
padding: 0;
}
&::-webkit-slider-thumb {
@include thumb;
}
&::-moz-range-thumb {
@include thumb;
}
&::-ms-thumb {
@include thumb;
}
&::-webkit-slider-runnable-track {
@include track;
}
}
}
// Textarea inputs
// -----------------------------------------------------------------------------