Adds initial work on groupings
This commit is contained in:
parent
3d79733cba
commit
1e52dcc213
2
dist/formulate.esm.js
vendored
2
dist/formulate.esm.js
vendored
File diff suppressed because one or more lines are too long
4
dist/formulate.min.js
vendored
4
dist/formulate.min.js
vendored
File diff suppressed because one or more lines are too long
2
dist/formulate.umd.js
vendored
2
dist/formulate.umd.js
vendored
File diff suppressed because one or more lines are too long
@ -1,16 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
|
<SpecimenGroup />
|
||||||
<SpecimenText />
|
<SpecimenText />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import SpecimenText from './specimens/SpecimenText'
|
import SpecimenText from './specimens/SpecimenText'
|
||||||
|
import SpecimenGroup from './specimens/SpecimenGroup'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'App',
|
name: 'App',
|
||||||
components: {
|
components: {
|
||||||
SpecimenText
|
SpecimenText,
|
||||||
|
SpecimenGroup
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
@ -24,4 +27,13 @@ export default {
|
|||||||
body {
|
body {
|
||||||
font-family: $formulate-font-stack;
|
font-family: $formulate-font-stack;
|
||||||
}
|
}
|
||||||
|
.specimens {
|
||||||
|
margin-bottom: 2em;
|
||||||
|
padding-bottom: 2em;
|
||||||
|
border-bottom: 1px solid gray;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
22
examples/specimens/SpecimenGroup.vue
Normal file
22
examples/specimens/SpecimenGroup.vue
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<template>
|
||||||
|
<div class="specimens specimens--group">
|
||||||
|
<FormulateInput
|
||||||
|
label="Invite some new users"
|
||||||
|
type="group"
|
||||||
|
placeholder="users"
|
||||||
|
help="Fields can be grouped"
|
||||||
|
>
|
||||||
|
<FormulateInput
|
||||||
|
name="Name"
|
||||||
|
type="text"
|
||||||
|
placeholder="User’s name"
|
||||||
|
/>
|
||||||
|
<FormulateInput
|
||||||
|
name="Email"
|
||||||
|
type="email"
|
||||||
|
placeholder="User’s email"
|
||||||
|
validation="required|email"
|
||||||
|
/>
|
||||||
|
</FormulateInput>
|
||||||
|
</div>
|
||||||
|
</template>
|
@ -6,15 +6,11 @@
|
|||||||
placeholder="Username"
|
placeholder="Username"
|
||||||
help="Select a username"
|
help="Select a username"
|
||||||
/>
|
/>
|
||||||
|
<FormulateInput
|
||||||
|
label="How old are you?"
|
||||||
|
type="number"
|
||||||
|
placeholder="25"
|
||||||
|
help="Select your age"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
@ -6,15 +6,18 @@ import { arrayify, parseLocale } from './libs/utils'
|
|||||||
import isPlainObject from 'is-plain-object'
|
import isPlainObject from 'is-plain-object'
|
||||||
import { en } from '@braid/vue-formulate-i18n'
|
import { en } from '@braid/vue-formulate-i18n'
|
||||||
import fauxUploader from './libs/faux-uploader'
|
import fauxUploader from './libs/faux-uploader'
|
||||||
|
import FormulateSlot from './FormulateSlot'
|
||||||
import FormulateInput from './FormulateInput.vue'
|
import FormulateInput from './FormulateInput.vue'
|
||||||
import FormulateForm from './FormulateForm.vue'
|
import FormulateForm from './FormulateForm.vue'
|
||||||
import FormulateErrors from './FormulateErrors.vue'
|
import FormulateErrors from './FormulateErrors.vue'
|
||||||
import FormulateHelp from './slots/FormulateHelp.vue'
|
import FormulateHelp from './slots/FormulateHelp.vue'
|
||||||
|
import FormulateGrouping from './FormulateGrouping.vue'
|
||||||
import FormulateLabel from './slots/FormulateLabel.vue'
|
import FormulateLabel from './slots/FormulateLabel.vue'
|
||||||
import FormulateInputGroup from './FormulateInputGroup.vue'
|
|
||||||
import FormulateInputBox from './inputs/FormulateInputBox.vue'
|
import FormulateInputBox from './inputs/FormulateInputBox.vue'
|
||||||
import FormulateInputText from './inputs/FormulateInputText.vue'
|
import FormulateInputText from './inputs/FormulateInputText.vue'
|
||||||
import FormulateInputFile from './inputs/FormulateInputFile.vue'
|
import FormulateInputFile from './inputs/FormulateInputFile.vue'
|
||||||
|
import FormulateRepeatable from './slots/FormulateRepeatable.vue'
|
||||||
|
import FormulateInputGroup from './inputs/FormulateInputGroup.vue'
|
||||||
import FormulateInputButton from './inputs/FormulateInputButton.vue'
|
import FormulateInputButton from './inputs/FormulateInputButton.vue'
|
||||||
import FormulateInputSelect from './inputs/FormulateInputSelect.vue'
|
import FormulateInputSelect from './inputs/FormulateInputSelect.vue'
|
||||||
import FormulateInputSlider from './inputs/FormulateInputSlider.vue'
|
import FormulateInputSlider from './inputs/FormulateInputSlider.vue'
|
||||||
@ -31,14 +34,17 @@ class Formulate {
|
|||||||
this.options = {}
|
this.options = {}
|
||||||
this.defaults = {
|
this.defaults = {
|
||||||
components: {
|
components: {
|
||||||
|
FormulateSlot,
|
||||||
FormulateForm,
|
FormulateForm,
|
||||||
FormulateHelp,
|
FormulateHelp,
|
||||||
FormulateLabel,
|
FormulateLabel,
|
||||||
FormulateInput,
|
FormulateInput,
|
||||||
FormulateErrors,
|
FormulateErrors,
|
||||||
|
FormulateGrouping,
|
||||||
FormulateInputBox,
|
FormulateInputBox,
|
||||||
FormulateInputText,
|
FormulateInputText,
|
||||||
FormulateInputFile,
|
FormulateInputFile,
|
||||||
|
FormulateRepeatable,
|
||||||
FormulateInputGroup,
|
FormulateInputGroup,
|
||||||
FormulateInputButton,
|
FormulateInputButton,
|
||||||
FormulateInputSelect,
|
FormulateInputSelect,
|
||||||
|
32
src/FormulateGrouping.vue
Normal file
32
src/FormulateGrouping.vue
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<template>
|
||||||
|
<FormulateSlot
|
||||||
|
name="repeatable"
|
||||||
|
>
|
||||||
|
<FormulateRepeatable
|
||||||
|
v-for="item in items"
|
||||||
|
:key="item.id"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</FormulateRepeatable>
|
||||||
|
</FormulateSlot>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
context: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
items () {
|
||||||
|
return [{ id: 1 }, { id: 2 }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
</style>
|
@ -46,16 +46,22 @@
|
|||||||
v-bind="context"
|
v-bind="context"
|
||||||
>
|
>
|
||||||
<component
|
<component
|
||||||
:is="slotComponents.help"
|
:is="context.slotComponents.help"
|
||||||
v-if="context.help"
|
v-if="context.help"
|
||||||
:context="context"
|
:context="context"
|
||||||
/>
|
/>
|
||||||
</slot>
|
</slot>
|
||||||
<FormulateErrors
|
<slot
|
||||||
v-if="!disableErrors"
|
name="errors"
|
||||||
:type="`input`"
|
v-bind="context"
|
||||||
:context="context"
|
>
|
||||||
/>
|
<component
|
||||||
|
:is="context.slotComponents.errors"
|
||||||
|
v-if="!context.disableErrors"
|
||||||
|
:type="context.slotComponents.errors === 'FormulateErrors' ? 'input' : false"
|
||||||
|
:context="context"
|
||||||
|
/>
|
||||||
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
14
src/FormulateSlot.js
Normal file
14
src/FormulateSlot.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
export default {
|
||||||
|
inheritAttrs: false,
|
||||||
|
functional: true,
|
||||||
|
render (h, { props, data, parent, children }) {
|
||||||
|
var p = parent
|
||||||
|
while (p && p.$options.name !== 'FormulateInput') {
|
||||||
|
p = p.$parent
|
||||||
|
}
|
||||||
|
if (p.$scopedSlots && p.$scopedSlots[props.name]) {
|
||||||
|
return p.$scopedSlots[props.name](props)
|
||||||
|
}
|
||||||
|
return h('div', data, children)
|
||||||
|
}
|
||||||
|
}
|
@ -1,15 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="formulate-input-group">
|
<div class="formulate-input-group">
|
||||||
<component
|
<template
|
||||||
:is="subComponent"
|
v-if="subType !== 'grouping'"
|
||||||
v-for="optionContext in optionsWithContext"
|
>
|
||||||
:key="optionContext.id"
|
<FormulateInput
|
||||||
v-model="context.model"
|
v-for="optionContext in optionsWithContext"
|
||||||
v-bind="optionContext"
|
:key="optionContext.id"
|
||||||
:disable-errors="true"
|
v-model="context.model"
|
||||||
class="formulate-input-group-item"
|
v-bind="optionContext"
|
||||||
@blur="context.blurHandler"
|
:disable-errors="true"
|
||||||
/>
|
class="formulate-input-group-item"
|
||||||
|
@blur="context.blurHandler"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template
|
||||||
|
v-else
|
||||||
|
>
|
||||||
|
<FormulateGrouping
|
||||||
|
:context="context"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</FormulateGrouping>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -26,9 +38,8 @@ export default {
|
|||||||
options () {
|
options () {
|
||||||
return this.context.options || []
|
return this.context.options || []
|
||||||
},
|
},
|
||||||
subComponent () {
|
subType () {
|
||||||
// @todo - rough-in for future flexible input-groups
|
return (this.context.type === 'group') ? 'grouping' : 'inputs'
|
||||||
return 'FormulateInput'
|
|
||||||
},
|
},
|
||||||
optionsWithContext () {
|
optionsWithContext () {
|
||||||
const {
|
const {
|
@ -13,6 +13,7 @@ export default {
|
|||||||
blurHandler: blurHandler.bind(this),
|
blurHandler: blurHandler.bind(this),
|
||||||
classification: this.classification,
|
classification: this.classification,
|
||||||
component: this.component,
|
component: this.component,
|
||||||
|
disableErrors: this.disableErrors,
|
||||||
errors: this.explicitErrors,
|
errors: this.explicitErrors,
|
||||||
getValidationErrors: this.getValidationErrors.bind(this),
|
getValidationErrors: this.getValidationErrors.bind(this),
|
||||||
hasLabel: (this.label && this.classification !== 'button'),
|
hasLabel: (this.label && this.classification !== 'button'),
|
||||||
@ -238,7 +239,8 @@ function hasVisibleErrors () {
|
|||||||
function slotComponents () {
|
function slotComponents () {
|
||||||
return {
|
return {
|
||||||
label: this.$formulate.slotComponent(this.type, 'label'),
|
label: this.$formulate.slotComponent(this.type, 'label'),
|
||||||
help: this.$formulate.slotComponent(this.type, 'help')
|
help: this.$formulate.slotComponent(this.type, 'help'),
|
||||||
|
errors: this.$formulate.slotComponent(this.type, 'errors')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,5 +47,8 @@ export default {
|
|||||||
|
|
||||||
// === FILE TYPE
|
// === FILE TYPE
|
||||||
file: add('file'),
|
file: add('file'),
|
||||||
image: add('file')
|
image: add('file'),
|
||||||
|
|
||||||
|
// === GROUP TYPE
|
||||||
|
group: add('group')
|
||||||
}
|
}
|
||||||
|
19
src/slots/FormulateRepeatable.vue
Normal file
19
src/slots/FormulateRepeatable.vue
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="formulate-group-row"
|
||||||
|
>
|
||||||
|
<FormulateSlot
|
||||||
|
name="default"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
</style>
|
@ -62,14 +62,17 @@ describe('Formulate', () => {
|
|||||||
|
|
||||||
it('installs on vue instance', () => {
|
it('installs on vue instance', () => {
|
||||||
const components = [
|
const components = [
|
||||||
|
'FormulateSlot',
|
||||||
'FormulateForm',
|
'FormulateForm',
|
||||||
'FormulateHelp',
|
'FormulateHelp',
|
||||||
'FormulateLabel',
|
'FormulateLabel',
|
||||||
'FormulateInput',
|
'FormulateInput',
|
||||||
'FormulateErrors',
|
'FormulateErrors',
|
||||||
|
'FormulateGrouping',
|
||||||
'FormulateInputBox',
|
'FormulateInputBox',
|
||||||
'FormulateInputText',
|
'FormulateInputText',
|
||||||
'FormulateInputFile',
|
'FormulateInputFile',
|
||||||
|
'FormulateRepeatable',
|
||||||
'FormulateInputGroup',
|
'FormulateInputGroup',
|
||||||
'FormulateInputButton',
|
'FormulateInputButton',
|
||||||
'FormulateInputSelect',
|
'FormulateInputSelect',
|
||||||
|
@ -4,7 +4,7 @@ import { mount } from '@vue/test-utils'
|
|||||||
import Formulate from '../../src/Formulate.js'
|
import Formulate from '../../src/Formulate.js'
|
||||||
import FormulateInput from '@/FormulateInput.vue'
|
import FormulateInput from '@/FormulateInput.vue'
|
||||||
import FormulateInputBox from '@/inputs/FormulateInputBox.vue'
|
import FormulateInputBox from '@/inputs/FormulateInputBox.vue'
|
||||||
import FormulateInputGroup from '@/FormulateInputGroup.vue'
|
import FormulateInputGroup from '@/inputs/FormulateInputGroup.vue'
|
||||||
|
|
||||||
Vue.use(Formulate)
|
Vue.use(Formulate)
|
||||||
|
|
||||||
|
@ -272,4 +272,15 @@ describe('FormulateInputText', () => {
|
|||||||
})
|
})
|
||||||
expect(wrapper.find('small').text()).toBe('Do you want some soda?')
|
expect(wrapper.find('small').text()).toBe('Do you want some soda?')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Allow errors override with scoped slot', async () => {
|
||||||
|
const wrapper = mount(FormulateInput, {
|
||||||
|
propsData: { type: 'text', name: 'soda', validation: 'required|in:foo,bar', errorBehavior: 'live' },
|
||||||
|
scopedSlots: {
|
||||||
|
errors: '<ul class="my-errors"><li v-for="error in props.visibleValidationErrors">{{ error }}</li></ul>'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await flushPromises();
|
||||||
|
expect(wrapper.findAll('.my-errors li').length).toBe(2)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user