buefy
Version:
Lightweight UI components for Vue.js (v3) based on Bulma
207 lines (185 loc) • 6.09 kB
text/typescript
import { defineComponent } from 'vue'
import config from '../utils/config'
import BField from '../components/field/Field.vue'
import type {
FieldMessageProp,
FieldTypeProp
} from '../components/field/Field.vue'
type BFieldComponent = InstanceType<typeof BField>
// HTMLElement that supports contraint validation;
// i.e., having `validationMessage`
// https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation#the_constraint_validation_api
type ConstraintValidationElement =
| HTMLButtonElement
| HTMLFieldSetElement
| HTMLInputElement
| HTMLOutputElement
| HTMLSelectElement
| HTMLTextAreaElement
const FormElementMixin = defineComponent({
props: {
size: String,
expanded: Boolean,
loading: Boolean,
rounded: Boolean,
icon: String,
iconPack: String,
maxlength: [Number, String],
useHtml5Validation: {
type: Boolean,
default: () => config.defaultUseHtml5Validation
},
validationMessage: String,
locale: {
type: [String, Array<string>],
default: () => {
return config.defaultLocale
}
},
statusIcon: {
type: Boolean,
default: () => {
return config.defaultStatusIcon
}
}
},
emits: {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
blur: (event?: Event) => true,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
focus: (event?: Event) => true
},
data() {
return {
isValid: true,
isFocused: false,
newIconPack: this.iconPack || config.defaultIconPack,
// host component must override this
_elementRef: ''
}
},
computed: {
/*
* Find parent Field, max 3 levels deep.
*/
parentField(): BFieldComponent | undefined {
let parent = this.$parent
for (let i = 0; i < 3; i++) {
if (parent && !(parent as BFieldComponent).$data._isField) {
parent = parent.$parent
}
}
// TODO: what if parent is defined but not a Field?
return parent as BFieldComponent | undefined
},
/*
* Get the type prop from parent if it's a Field.
*/
statusType() {
const { newType } = this.parentField || {}
if (!newType) return
if (typeof newType === 'string') {
return newType
} else {
for (const key in newType) {
if (newType[key]) {
return key
}
}
}
return undefined
},
/*
* Get the message prop from parent if it's a Field.
*/
statusMessage() {
if (!this.parentField) return
return this.parentField.newMessage || this.parentField.$slots.message
},
/*
* Fix icon size for inputs, large was too big
*/
iconSize() {
switch (this.size) {
case 'is-small': return this.size
case 'is-medium': return
case 'is-large': return this.newIconPack === 'mdi'
? 'is-medium'
: ''
}
return undefined
}
},
methods: {
/*
* Focus method that work dynamically depending on the component.
*/
focus() {
const el = this.getElement()
if (el === undefined) return
this.$nextTick(() => {
if (el) el.focus()
})
},
onBlur($event?: Event) {
this.isFocused = false
this.$emit('blur', $event)
this.checkHtml5Validity()
},
onFocus($event?: Event) {
this.isFocused = true
this.$emit('focus', $event)
},
getElement(): ConstraintValidationElement {
let el = this.$refs[this.$data._elementRef]
while (el != null && typeof el === 'object' && '$refs' in el) {
const form = el as unknown as typeof FormElementMixin
el = form.$refs[form.$data._elementRef]
}
// TODO: what if el is not an HTMLElement? possibly null?
return el as ConstraintValidationElement
},
setInvalid() {
const type = 'is-danger'
const message = this.validationMessage || this.getElement().validationMessage
this.setValidity(type, message)
},
setValidity(type: FieldTypeProp | null, message: FieldMessageProp | null) {
this.$nextTick(() => {
if (this.parentField) {
// Set type only if not defined
if (!this.parentField.type) {
this.parentField.newType = type
}
// Set message only if not defined
if (!this.parentField.message) {
this.parentField.newMessage = message
}
}
})
},
/*
* Check HTML5 validation, set isValid property.
* If validation fail, send 'is-danger' type,
* and error message to parent if it's a Field.
*/
checkHtml5Validity(): boolean {
if (!this.useHtml5Validation) {
return false
}
const el = this.getElement()
if (el == null) {
return false
}
if (!el.checkValidity()) {
this.setInvalid()
this.isValid = false
} else {
this.setValidity(null, null)
this.isValid = true
}
return this.isValid
}
}
})
export default FormElementMixin