389 lines
12 KiB
Vue
389 lines
12 KiB
Vue
<template>
|
|
<div
|
|
class="textbox-question"
|
|
:class="[
|
|
((errors && errors.length) || hasError) ? 'has-error' : '',
|
|
(meta?.valid && meta?.touched && !((errors && errors.length) || hasError)) ? 'has-success' : ''
|
|
]">
|
|
<label
|
|
v-if="displayQuestionText"
|
|
:for="inputId"
|
|
:aria-label="questionTextForAria"
|
|
class="form-label"
|
|
:class="[questionAlignment === 'center' ? 'text-center w-100 mb-5' : '']"
|
|
v-html="questionText"></label>
|
|
<div
|
|
class="input-wrapper"
|
|
:class="[
|
|
includeSearchIcon ? 'has-search-icon' : '',
|
|
includeSelectIcon ? 'has-select-icon' : '',
|
|
hasTextButton ? 'has-text-button' : ''
|
|
]">
|
|
<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="questionTextForAria"
|
|
: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" />
|
|
<buttonMain
|
|
v-if="hasTextButton"
|
|
ref="buttonMain"
|
|
variant="primary"
|
|
:buttonText="buttonText"
|
|
@clickEvent="buttonCallback">
|
|
</buttonMain>
|
|
</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';
|
|
import { extractSpanText } from '@/helpers/text-helper';
|
|
import buttonMain from '@/ux-components/button-main/button-main.vue';
|
|
|
|
export default {
|
|
name: 'textbox-question',
|
|
components: {
|
|
buttonMain
|
|
},
|
|
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,
|
|
buttonCallback: {
|
|
type: Function,
|
|
default: () => {}
|
|
}
|
|
},
|
|
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');
|
|
},
|
|
questionTextForAria() {
|
|
return extractSpanText(this.getCmsContent(this.cmsWidgetName, 'QuestionText'));
|
|
},
|
|
buttonText() {
|
|
return this.getCmsContent(this.cmsWidgetName, 'ButtonText');
|
|
},
|
|
hasTextButton() {
|
|
return this.buttonText && this.buttonText.length > 0;
|
|
},
|
|
value: {
|
|
get() {
|
|
return this.modelValue;
|
|
},
|
|
set(newValue) {
|
|
this.$emit('update:modelValue', newValue);
|
|
}
|
|
},
|
|
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;
|
|
}
|
|
|
|
input::-webkit-date-and-time-value {
|
|
text-align: left !important;
|
|
}
|
|
|
|
.textbox-question {
|
|
label {
|
|
color: $black;
|
|
font-weight: 600;
|
|
}
|
|
span {
|
|
font-weight: 400;
|
|
font-size: 0.875rem;
|
|
color: #4d5151;
|
|
}
|
|
.form-test-error span {
|
|
color: #d4281c;
|
|
font-size: 1rem;
|
|
font-weight: 400;
|
|
}
|
|
|
|
.input-wrapper {
|
|
position: relative;
|
|
&.has-search-icon {
|
|
input[type='text'] {
|
|
border-radius: $border-radius;
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
&.has-text-button {
|
|
display: flex;
|
|
input[type='text'] {
|
|
border-radius: 1.5rem;
|
|
}
|
|
}
|
|
}
|
|
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: $border-input;
|
|
border-radius: $border-radius;
|
|
min-height: 3rem;
|
|
max-height: 3rem;
|
|
padding: 0.75rem 1rem;
|
|
letter-spacing: inherit;
|
|
box-shadow: $box-shadow-input;
|
|
&::placeholder {
|
|
color: $gray-500;
|
|
}
|
|
&:focus, &:focus-within {
|
|
border: $border-input-focus;
|
|
}
|
|
&:disabled,
|
|
&.disabled {
|
|
background-color: $gray-100;
|
|
}
|
|
}
|
|
p {
|
|
display: none;
|
|
}
|
|
}
|
|
</style>
|