380 lines
11 KiB
Vue
380 lines
11 KiB
Vue
<template>
|
|
<div
|
|
class="textbox-question"
|
|
:class="(errors && errors.length) || hasError ? 'has-error' : ''">
|
|
<label
|
|
v-if="displayQuestionText"
|
|
:for="inputId"
|
|
:aria-label="questionText"
|
|
class="form-label"
|
|
:class="[questionAlignment === 'center' ? 'text-center w-100 mb-5' : '']"
|
|
v-html="labelText"></label>
|
|
<div
|
|
class="input-wrapper"
|
|
:class="[
|
|
includeSearchIcon ? 'has-search-icon' : '',
|
|
includeSelectIcon ? 'has-select-icon' : ''
|
|
]">
|
|
<input
|
|
:id="inputId"
|
|
:ref="inputId"
|
|
v-model.trim="value"
|
|
v-maska
|
|
:data-maska="getMaskFromProp"
|
|
:data-maska-tokens="getTokensFromProp"
|
|
class="form-control"
|
|
:type="type"
|
|
:name="inputId"
|
|
:placeholder="placeholderText"
|
|
:aria-disabled="isDisabled"
|
|
:disabled="isDisabled"
|
|
:aria-required="isRequired"
|
|
:aria-label="questionText"
|
|
:min="min"
|
|
:max="max"
|
|
:required="isRequired"
|
|
:class="[
|
|
hasIcon ? 'has-icon' : '',
|
|
iconRight ? 'icon-right' : '',
|
|
cornerStyle === 'rounded' ? 'rounded-pill' : ''
|
|
]"
|
|
:validationRules="validationRules"
|
|
:maxlength="maxLength ? maxLength : '999'"
|
|
:data-bs-toggle="includeSelectIcon ? 'modal' : ''"
|
|
:data-bs-target="'#' + cmsWidgetName"
|
|
@change="handleChange"
|
|
@blur="handleChange"
|
|
@focus="$emit('focus', $event.target.value)"
|
|
@paste="trimOnPaste"
|
|
@drop="trimOnPaste" />
|
|
<button
|
|
v-if="includeSearchIcon"
|
|
type="submit"
|
|
aria-label="Search button"
|
|
@click="clickedSearch" />
|
|
<button
|
|
v-if="includeSelectIcon"
|
|
type="submit"
|
|
data-bs-toggle="modal"
|
|
:data-bs-target="'#' + cmsWidgetName"
|
|
aria-label="Select button" />
|
|
</div>
|
|
<div
|
|
v-show="errorMessage"
|
|
ref="errorMessageDiv"
|
|
class="row my-1 form-test-error">
|
|
<span
|
|
class="d-inline-flex mt-0"
|
|
role="alert">{{ errorMessage }}</span>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { useField, validate } from 'vee-validate';
|
|
|
|
export default {
|
|
name: 'textbox-question',
|
|
props: {
|
|
type: {
|
|
type: String,
|
|
default: 'text'
|
|
},
|
|
placeholderText: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
displayQuestionText: {
|
|
type: Boolean,
|
|
default: true
|
|
},
|
|
modelValue: String,
|
|
inputId: {
|
|
type: String,
|
|
required: true
|
|
},
|
|
isDisabled: Boolean,
|
|
isRequired: Boolean,
|
|
hasIcon: Boolean, // If input has an icon
|
|
iconRight: Boolean, // Place icon on right side of text input, otherwise default is left if hasIcon prop is used
|
|
hasError: Boolean,
|
|
mask: {
|
|
type: [String, Object],
|
|
default: ''
|
|
},
|
|
validationRules: String,
|
|
cmsWidgetName: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
maxLength: String,
|
|
questionAlignment: String, // Left or center. Left is default.
|
|
cornerStyle: String, // Rounded or square. Square is default.
|
|
includeSearchIcon: Boolean,
|
|
includeSelectIcon: Boolean,
|
|
min: String,
|
|
max: String,
|
|
disableAutoFill: Boolean
|
|
},
|
|
emits: ['focus', 'update:modelValue', 'textboxQuestionEvent.inputIdAssigned', 'click-event'],
|
|
setup(props) {
|
|
const propsClone = { ...props };
|
|
const { modelValue } = propsClone;
|
|
let initialValue;
|
|
|
|
switch (typeof modelValue) {
|
|
case 'number':
|
|
initialValue = modelValue;
|
|
break;
|
|
default:
|
|
initialValue = modelValue && modelValue.length > 0 ? modelValue : '';
|
|
break;
|
|
}
|
|
|
|
const fieldOptions = {
|
|
type: 'text',
|
|
value: modelValue,
|
|
initialValue
|
|
};
|
|
|
|
// eslint-disable-next-line no-shadow
|
|
const { errorMessage, handleBlur, handleChange, meta, validate, errors } =
|
|
useField(props.inputId, props.validationRules, fieldOptions);
|
|
|
|
return {
|
|
errorMessage,
|
|
handleBlur,
|
|
handleChange,
|
|
validate,
|
|
meta,
|
|
errors
|
|
};
|
|
},
|
|
computed: {
|
|
questionText() {
|
|
return this.getCmsContent(this.cmsWidgetName, 'QuestionText');
|
|
},
|
|
value: {
|
|
get() {
|
|
return this.modelValue;
|
|
},
|
|
set(newValue) {
|
|
this.$emit('update:modelValue', newValue);
|
|
}
|
|
},
|
|
labelText: {
|
|
get() {
|
|
const noBreakChar = '⁠';
|
|
let questionText = '';
|
|
if (this.disableAutoFill) {
|
|
const words = this.questionText.toString().split(/[ ]+/);
|
|
words.forEach((word) => {
|
|
const position = 1;
|
|
const newWord = [
|
|
word.toString().slice(0, position),
|
|
noBreakChar,
|
|
word.toString().slice(position)
|
|
].join('');
|
|
questionText += `${newWord} `;
|
|
});
|
|
|
|
questionText = questionText.trimEnd();
|
|
} else {
|
|
questionText = this.questionText.toString();
|
|
}
|
|
|
|
return questionText;
|
|
}
|
|
},
|
|
getMaskFromProp() {
|
|
if (this.mask) {
|
|
if (this.mask.mask) {
|
|
return this.mask.mask;
|
|
}
|
|
return this.mask;
|
|
}
|
|
return null;
|
|
},
|
|
getTokensFromProp() {
|
|
if (this.mask?.tokens) {
|
|
return `{ '${this.mask.tokens.token}': { 'pattern': '${this.mask.tokens.pattern}' }}`;
|
|
}
|
|
return null;
|
|
}
|
|
},
|
|
watch: {
|
|
async value(newValue) {
|
|
const result = await validate(newValue, this.validationRules); // do a test validation check, without triggering full validation
|
|
if (result.valid) {
|
|
this.handleChange(newValue); // trigger full validation on this field only
|
|
}
|
|
}
|
|
},
|
|
mounted() {
|
|
this.$emit('textboxQuestionEvent.inputIdAssigned', this.inputId);
|
|
},
|
|
methods: {
|
|
trimOnPaste(evt) {
|
|
evt.stopPropagation();
|
|
evt.preventDefault();
|
|
|
|
const input = document.getElementById(evt.srcElement.id);
|
|
const originalValue = input.value;
|
|
const startPosition = input.selectionStart;
|
|
const endPosition = input.selectionEnd;
|
|
const selectionLength = endPosition - startPosition;
|
|
|
|
let data = null;
|
|
if (evt.type === 'paste') {
|
|
data = evt.clipboardData || window.clipboardData;
|
|
} else {
|
|
data = evt.dataTransfer;
|
|
}
|
|
|
|
const textValue = data.getData('Text');
|
|
|
|
// Insert the pasted/dropped string at the index position
|
|
const arrOriginalString = originalValue.split('');
|
|
arrOriginalString.splice(startPosition, selectionLength, textValue);
|
|
const blendedString = arrOriginalString.join('');
|
|
|
|
this.$emit('update:modelValue', blendedString);
|
|
},
|
|
clickedSearch() {
|
|
if (this.meta.valid) {
|
|
this.$emit('click-event');
|
|
}
|
|
}
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
@import "@/styles/ux-variables-svg-strings.scss";
|
|
input[type='date']::-webkit-inner-spin-button {
|
|
display: none;
|
|
}
|
|
|
|
input[type='date']::-webkit-calendar-picker-indicator {
|
|
position: absolute;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
right: 1px;
|
|
background-image: url($svg-calendar-picker);
|
|
background-repeat: no-repeat;
|
|
background-position: center;
|
|
width: 16px; // check this
|
|
height: 16px;
|
|
display: flex;
|
|
border-radius: 0 7px 7px 0;
|
|
background-color: #e4f1f7;
|
|
&:hover {
|
|
cursor: pointer;
|
|
}
|
|
padding: 15px 0;
|
|
min-width: 48px;
|
|
}
|
|
|
|
.textbox-question {
|
|
label {
|
|
color: $black;
|
|
font-weight: 500;
|
|
}
|
|
span {
|
|
font-weight: 400;
|
|
font-size: 0.875rem;
|
|
color: #4d5151;
|
|
}
|
|
.form-test-error span {
|
|
color: #d4281c;
|
|
font-size: 0.875rem;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.input-wrapper {
|
|
position: relative;
|
|
&.has-search-icon {
|
|
input[type='text'] {
|
|
border-radius: $border-radius-lg;
|
|
}
|
|
button[type='submit'] {
|
|
position: absolute;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
right: 0;
|
|
background-image: url($svg-search-icon);
|
|
background-repeat: no-repeat;
|
|
background-position: center;
|
|
border-radius: 0 $border-radius-lg $border-radius-lg 0;
|
|
background-color: $blue-100;
|
|
width: 2.75rem;
|
|
height: 100%;
|
|
border: 1px solid $gray-500;
|
|
border-left: none;
|
|
display: flex;
|
|
}
|
|
}
|
|
&.has-select-icon {
|
|
button[type='submit'] {
|
|
position: absolute;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
right: 1rem;
|
|
background-image: url($svg-select-icon);
|
|
background-repeat: no-repeat;
|
|
background-position: center;
|
|
background-color: transparent;
|
|
width: 1rem;
|
|
height: 100%;
|
|
border: 0px;
|
|
border-left: none;
|
|
display: flex;
|
|
}
|
|
}
|
|
}
|
|
input {
|
|
&.has-icon {
|
|
background-image: url(~@/assets/img/icons/location-pin.svg);
|
|
background-repeat: no-repeat;
|
|
background-position: 0.75rem 50%;
|
|
background-size: auto 1rem;
|
|
padding: 0 0.75rem 0 2.5rem;
|
|
&.icon-right {
|
|
background-position: calc(100% - 0.75rem) 50%;
|
|
padding: 0 2.5rem 0 0.75rem;
|
|
}
|
|
}
|
|
}
|
|
.form-label {
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
.form-control {
|
|
border: 1px solid $gray-500;
|
|
border-radius: 0.5rem;
|
|
min-height: 3rem;
|
|
max-height: 3rem;
|
|
padding: 0.75rem 1rem;
|
|
&::placeholder {
|
|
color: $gray-500;
|
|
}
|
|
&:focus {
|
|
box-shadow: 0 0 0 2.5px $blue;
|
|
}
|
|
&:disabled,
|
|
&.disabled {
|
|
background-color: $gray-100;
|
|
&:hover {
|
|
box-shadow: 0 0 0 4px transparent;
|
|
border: 1px solid $gray-500;
|
|
}
|
|
}
|
|
&:hover {
|
|
border: 1px solid $gray-500;
|
|
box-shadow: 0 0 0 4px $blue-300;
|
|
}
|
|
}
|
|
p {
|
|
display: none;
|
|
}
|
|
}
|
|
</style>
|