1
0
mirror of synced 2025-02-16 12:43:14 +03:00

Includes support for slider values, custom validation rules and messages

This commit is contained in:
Justin Schroeder 2020-02-27 01:18:51 -05:00
parent 95abd4e58d
commit c31e896b86
18 changed files with 308 additions and 10173 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -1,6 +1,7 @@
import commonjs from 'rollup-plugin-commonjs' // Convert CommonJS modules to ES6
import vue from 'rollup-plugin-vue' // Handle .vue SFC files
import buble from 'rollup-plugin-buble' // Transpile/polyfill with reasonable browser support
import { terser } from 'rollup-plugin-terser'
export default {
input: 'src/Formulate.js', // Path relative to package.json
@ -23,6 +24,7 @@ export default {
}),
buble({
objectAssign: 'Object.assign'
}) // Transpile to ES5
}), // Transpile to ES5,
terser()
]
}

3366
dist/formulate.esm.js vendored

File diff suppressed because one or more lines are too long

3373
dist/formulate.min.js vendored

File diff suppressed because one or more lines are too long

3376
dist/formulate.umd.js vendored

File diff suppressed because one or more lines are too long

28
dist/snow.css vendored
View File

@ -49,6 +49,22 @@
.formulate-input[data-classification='text'] input:focus {
outline: 0;
border: 1px solid #41b883; }
.formulate-input[data-classification='text'] .formulate-input-element--search {
position: relative; }
.formulate-input[data-classification='text'] .formulate-input-element--search::before {
content: '';
width: 2em;
height: 100%;
position: absolute;
left: 0;
top: 0;
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72.99 72.9"><path fill="%236d6d6d" d="M71.77,66,53.87,48.1A29.94,29.94,0,1,0,30,60a29.52,29.52,0,0,0,18.1-6.1l18,17.8A4,4,0,0,0,69,72.9a4.18,4.18,0,0,0,2.9-1.2A4.09,4.09,0,0,0,71.77,66ZM30.07,51.9a21.9,21.9,0,1,1,15.5-37.4A21.37,21.37,0,0,1,52,30a22,22,0,0,1-6.4,15.5A21.54,21.54,0,0,1,30.07,51.9Z"/></svg>');
background-size: 1em 1em;
background-repeat: no-repeat;
background-position: center;
pointer-events: none; }
.formulate-input[data-classification='text'] .formulate-input-element--search input {
padding-left: 2em; }
.formulate-input[data-classification='text'] input[type="color"] {
height: 1.1em;
box-sizing: content-box; }
@ -72,6 +88,18 @@
border-radius: .2em;
border: 0;
flex: auto; }
.formulate-input[data-classification='slider'] .formulate-input-element--range {
display: flex;
align-items: center; }
.formulate-input[data-classification='slider'] .formulate-input-element-range-value {
font-size: .9em;
line-height: 1;
margin-left: .5em;
background-color: #efefef;
padding: .25em .3em;
border-radius: .25em;
color: #6d6d6d;
font-variant-numeric: tabular-nums; }
.formulate-input[data-classification='slider'] input {
appearance: none;
width: 100%;

4
dist/snow.min.css vendored

File diff suppressed because one or more lines are too long

76
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "@braid/vue-formulate",
"version": "2.0.0-alpha.1",
"version": "2.0.0-alpha.3",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -8,7 +8,6 @@
"version": "7.5.5",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz",
"integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==",
"dev": true,
"requires": {
"@babel/highlight": "^7.0.0"
}
@ -1541,7 +1540,6 @@
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz",
"integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==",
"dev": true,
"requires": {
"chalk": "^2.0.0",
"esutils": "^2.0.2",
@ -3334,7 +3332,6 @@
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "^1.9.0"
}
@ -4260,8 +4257,7 @@
"buffer-from": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
"dev": true
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
},
"cache-base": {
"version": "1.0.1",
@ -4398,7 +4394,6 @@
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
@ -4686,7 +4681,6 @@
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"requires": {
"color-name": "1.1.3"
}
@ -4694,8 +4688,7 @@
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
"dev": true
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
},
"color-string": {
"version": "1.5.3",
@ -4719,8 +4712,7 @@
"commander": {
"version": "2.20.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.1.tgz",
"integrity": "sha512-cCuLsMhJeWQ/ZpsFTbE765kvVfoeSddc4nU3up4fV+fDBcfUXnbITJ+JzhkdjzOqhURjZgujxaioam4RM9yGUg==",
"dev": true
"integrity": "sha512-cCuLsMhJeWQ/ZpsFTbE765kvVfoeSddc4nU3up4fV+fDBcfUXnbITJ+JzhkdjzOqhURjZgujxaioam4RM9yGUg=="
},
"component-emitter": {
"version": "1.3.0",
@ -5570,8 +5562,7 @@
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
"dev": true
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
},
"escodegen": {
"version": "1.12.0",
@ -5891,14 +5882,12 @@
"estree-walker": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz",
"integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==",
"dev": true
"integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w=="
},
"esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"dev": true
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="
},
"exec-sh": {
"version": "0.3.2",
@ -7167,8 +7156,7 @@
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
"dev": true
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
},
"has-symbols": {
"version": "1.0.0",
@ -8362,7 +8350,6 @@
"version": "24.9.0",
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz",
"integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==",
"dev": true,
"requires": {
"merge-stream": "^2.0.0",
"supports-color": "^6.1.0"
@ -8372,7 +8359,6 @@
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
"integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
@ -8408,8 +8394,7 @@
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"js-yaml": {
"version": "3.13.1",
@ -8967,8 +8952,7 @@
"merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
"dev": true
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
},
"merge2": {
"version": "1.3.0",
@ -11497,6 +11481,18 @@
"rollup-pluginutils": "^2.8.1"
}
},
"rollup-plugin-terser": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-5.2.0.tgz",
"integrity": "sha512-jQI+nYhtDBc9HFRBz8iGttQg7li9klmzR62RG2W2nN6hJ/FI2K2ItYQ7kJ7/zn+vs+BP1AEccmVRjRN989I+Nw==",
"requires": {
"@babel/code-frame": "^7.5.5",
"jest-worker": "^24.9.0",
"rollup-pluginutils": "^2.8.2",
"serialize-javascript": "^2.1.2",
"terser": "^4.6.2"
}
},
"rollup-plugin-vue": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/rollup-plugin-vue/-/rollup-plugin-vue-5.1.6.tgz",
@ -11526,7 +11522,6 @@
"version": "2.8.2",
"resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz",
"integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==",
"dev": true,
"requires": {
"estree-walker": "^0.6.1"
}
@ -11847,6 +11842,11 @@
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
},
"serialize-javascript": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz",
"integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ=="
},
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
@ -12131,7 +12131,6 @@
"version": "0.5.13",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
"integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==",
"dev": true,
"requires": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
@ -12140,8 +12139,7 @@
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
}
}
},
@ -12446,7 +12444,6 @@
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
@ -12535,6 +12532,23 @@
"inherits": "2"
}
},
"terser": {
"version": "4.6.4",
"resolved": "https://registry.npmjs.org/terser/-/terser-4.6.4.tgz",
"integrity": "sha512-5fqgBPLgVHZ/fVvqRhhUp9YUiGXhFJ9ZkrZWD9vQtFBR4QIGTnbsb+/kKqSqfgp3WnBwGWAFnedGTtmX1YTn0w==",
"requires": {
"commander": "^2.20.0",
"source-map": "~0.6.1",
"source-map-support": "~0.5.12"
},
"dependencies": {
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
}
}
},
"test-exclude": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz",

View File

@ -1,6 +1,6 @@
{
"name": "@braid/vue-formulate",
"version": "2.0.0-alpha.2",
"version": "2.0.0-alpha.3",
"description": "The easiest way to build forms in Vue.",
"main": "dist/formulate.umd.js",
"module": "dist/formulate.esm.js",
@ -82,6 +82,7 @@
"clone-deep": "^4.0.1",
"is-plain-object": "^3.0.0",
"is-url": "^1.2.4",
"nanoid": "^2.1.11"
"nanoid": "^2.1.11",
"rollup-plugin-terser": "^5.2.0"
}
}

View File

@ -111,8 +111,8 @@ class Formulate {
* Get validation rules.
* @return {object} object of validation functions
*/
rules () {
return this.options.rules
rules (rules = {}) {
return { ...this.options.rules, ...rules }
}
/**

View File

@ -161,6 +161,18 @@ export default {
preventWindowDrops: {
type: Boolean,
default: true
},
showValue: {
type: [String, Boolean],
default: false
},
validationMessages: {
type: Object,
default: () => ({})
},
validationRules: {
type: Object,
default: () => ({})
}
},
data () {
@ -220,27 +232,43 @@ export default {
}
},
performValidation () {
const rules = parseRules(this.validation, this.$formulate.rules())
const rules = parseRules(this.validation, this.$formulate.rules(this.validationRules))
this.pendingValidation = Promise.all(
rules.map(([rule, args]) => {
return rule({
var res = rule({
value: this.context.model,
getFormValues: this.getFormValues.bind(this),
name: this.context.name
}, ...args)
.then(res => res ? false : this.$formulate.validationMessage(rule.name, {
args,
name: this.mergedValidationName,
value: this.context.model,
vm: this,
formValues: this.getFormValues()
}))
res = (res instanceof Promise) ? res : Promise.resolve(res)
return res.then(res => res ? false : this.getValidationMessage(rule, args))
})
)
.then(result => result.filter(result => result))
.then(errorMessages => { this.validationErrors = errorMessages })
return this.pendingValidation
},
getValidationMessage (rule, args) {
return this.getValidationFunction(rule)({
args,
name: this.mergedValidationName,
value: this.context.model,
vm: this,
formValues: this.getFormValues()
})
},
getValidationFunction (rule) {
const ruleName = rule.name.substr(0, 1) === '_' ? rule.name.substr(1) : rule.name
if (this.validationMessages && typeof this.validationMessages === 'object' && typeof this.validationMessages[ruleName] !== 'undefined') {
switch (typeof this.validationMessages[ruleName]) {
case 'function':
return this.validationMessages[ruleName]
case 'string':
return () => this.validationMessages[ruleName]
}
}
return (context) => this.$formulate.validationMessage(rule.name, context)
},
hasValidationErrors () {
return new Promise(resolve => {
this.$nextTick(() => {

View File

@ -9,6 +9,11 @@
v-bind="attributes"
@blur="context.blurHandler"
>
<div
v-if="context.showValue"
class="formulate-input-element-range-value"
v-text="context.model"
/>
</div>
</template>

View File

@ -53,14 +53,14 @@ function typeContext () {
optionGroups: this.optionGroups ? map(this.optionGroups, (k, v) => createOptionList.call(this, v)) : false,
placeholder: this.$attrs.placeholder || false
}
case 'group':
case 'slider':
return { showValue: !!this.showValue }
default:
if (this.options) {
return {
options: createOptionList.call(this, this.options)
}
}
break
default:
return {}
}
}

View File

@ -156,16 +156,15 @@ describe('FormulateForm', () => {
expect(wrapper.emitted('submit-raw')[0][0]).toBeInstanceOf(FormSubmission)
})
// it('resolves hasValidationErrors to true', async () => {
// const wrapper = mount(FormulateForm, {
// slots: { default: '<FormulateInput type="text" validation="required" name="testinput" />' }
// })
// wrapper.find('form').trigger('submit')
// await flushPromises()
// const submission = wrapper.emitted('submit-raw')[0][0]
// expect(await submission.hasValidationErrors()).toBe(true)
// })
it('resolves hasValidationErrors to true', async () => {
const wrapper = mount(FormulateForm, {
slots: { default: '<FormulateInput type="text" validation="required" name="testinput" />' }
})
wrapper.find('form').trigger('submit')
await flushPromises()
const submission = wrapper.emitted('submit-raw')[0][0]
expect(await submission.hasValidationErrors()).toBe(true)
})
it('resolves submitted form values to an object', async () => {
const wrapper = mount(FormulateForm, {

120
test/FormulateInput.test.js Normal file
View File

@ -0,0 +1,120 @@
import Vue from 'vue'
import flushPromises from 'flush-promises'
import { mount } from '@vue/test-utils'
import Formulate from '../src/Formulate.js'
import FormulateInput from '@/FormulateInput.vue'
import FormulateInputBox from '@/inputs/FormulateInputBox.vue'
const globalRule = jest.fn((context) => { return false })
Vue.use(Formulate, {
locales: {
en: {
email: ({ value }) => `Super invalid email: ${value}`
}
},
rules: {
globalRule
},
library: {
special: {
classification: 'box',
component: 'FormulateInputBox'
}
}
})
describe('FormulateInput', () => {
it('allows custom field-rule level validation strings', async () => {
const wrapper = mount(FormulateInput, { propsData: {
type: 'text',
validation: 'required|in:abcdef',
validationMessages: {in: 'the value was different than expected'},
errorBehavior: 'live',
value: 'other value'
} })
await flushPromises()
expect(wrapper.find('.formulate-input-error').text()).toBe('the value was different than expected')
})
it('allows custom field-rule level validation functions', async () => {
const wrapper = mount(FormulateInput, { propsData: {
type: 'text',
validation: 'required|in:abcdef',
validationMessages: { in: ({ value }) => `The string ${value} is not correct.` },
errorBehavior: 'live',
value: 'other value'
} })
await flushPromises()
expect(wrapper.find('.formulate-input-error').text()).toBe('The string other value is not correct.')
})
it('uses globally overridden validation messages', async () => {
const wrapper = mount(FormulateInput, { propsData: {
type: 'text',
validation: 'required|email',
errorBehavior: 'live',
value: 'wrong email'
} })
await flushPromises()
expect(wrapper.find('.formulate-input-error').text()).toBe('Super invalid email: wrong email')
})
it('uses custom async validation rules on defined on the field', async () => {
const wrapper = mount(FormulateInput, { propsData: {
type: 'text',
validation: 'required|foobar',
validationMessages: {
foobar: 'failed the foobar check'
},
validationRules: {
foobar: async ({ value }) => value === 'foo'
},
validation: 'required|foobar',
errorBehavior: 'live',
value: 'bar'
} })
await flushPromises()
expect(wrapper.find('.formulate-input-error').text()).toBe('failed the foobar check')
})
it('uses custom sync validation rules on defined on the field', async () => {
const wrapper = mount(FormulateInput, { propsData: {
type: 'text',
validation: 'required|foobar',
validationMessages: {
foobar: 'failed the foobar check'
},
validationRules: {
foobar: ({ value }) => value === 'foo'
},
validation: 'required|foobar',
errorBehavior: 'live',
value: 'bar'
} })
await flushPromises()
expect(wrapper.find('.formulate-input-error').text()).toBe('failed the foobar check')
})
it('uses global custom validation rules', async () => {
const wrapper = mount(FormulateInput, { propsData: {
type: 'text',
validation: 'required|globalRule',
errorBehavior: 'live',
value: 'bar'
} })
await flushPromises()
expect(globalRule.mock.calls.length).toBe(1)
})
it('can extend its standard library of inputs', async () => {
const wrapper = mount(FormulateInput, { propsData: {
type: 'special',
validation: 'required',
errorBehavior: 'live',
value: 'bar'
} })
await flushPromises()
expect(wrapper.contains(FormulateInputBox)).toBe(true)
})
})

View File

@ -15,4 +15,14 @@ describe('FormulateInputSlider', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'range' } })
expect(wrapper.contains(FormulateInputSlider)).toBe(true)
})
it('does not show value if the show-value prop is not set', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'range', value: '15', min: '0', max: '100' } })
expect(wrapper.find('.formulate-input-element-range-value').exists()).toBe(false)
})
it('renders the value when type is "range" and show-value prop is set', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'range', showValue: 'true', value: '15', min: '0', max: '100' } })
expect(wrapper.find('.formulate-input-element-range-value').text()).toBe('15')
})
})

View File

@ -20,5 +20,5 @@ module.exports = {
collectCoverageFrom: [
"src/*.{js,vue}",
],
verbose: true
// verbose: true
}

View File

@ -57,6 +57,27 @@
@include baseinput;
}
.formulate-input-element--search {
position: relative;
&::before {
content: '';
width: 2em;
height: 100%;
position: absolute;
left: 0;
top: 0;
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72.99 72.9"><path fill="%23#{str-slice("#{$formulate-gray-ddd}", 2)}" d="M71.77,66,53.87,48.1A29.94,29.94,0,1,0,30,60a29.52,29.52,0,0,0,18.1-6.1l18,17.8A4,4,0,0,0,69,72.9a4.18,4.18,0,0,0,2.9-1.2A4.09,4.09,0,0,0,71.77,66ZM30.07,51.9a21.9,21.9,0,1,1,15.5-37.4A21.37,21.37,0,0,1,52,30a22,22,0,0,1-6.4,15.5A21.54,21.54,0,0,1,30.07,51.9Z"/></svg>');
background-size: 1em 1em;
background-repeat: no-repeat;
background-position: center;
pointer-events: none;
}
input {
padding-left: 2em;
}
}
input[type="color"] {
height: 1.1em;
box-sizing: content-box;
@ -93,6 +114,22 @@
// -----------------------------------------------------------------------------
&[data-classification='slider'] {
.formulate-input-element--range {
display: flex;
align-items: center;
}
.formulate-input-element-range-value {
font-size: .9em;
line-height: 1;
margin-left: .5em;
background-color: $formulate-gray;
padding: .25em .3em;
border-radius: .25em;
color: $formulate-gray-ddd;
font-variant-numeric: tabular-nums;
}
input {
appearance: none;
width: 100%;