2023-03-03 15:54:28 +00:00
|
|
|
<script setup>
|
|
|
|
import { computed, ref, onMounted, onBeforeUnmount } from 'vue';
|
|
|
|
import { MainService } from '@/Stores/main';
|
|
|
|
import FormControlIcon from '@/Components/FormControlIcon.vue';
|
|
|
|
const props = defineProps({
|
2023-03-17 15:13:37 +00:00
|
|
|
name: {
|
|
|
|
type: String,
|
|
|
|
default: null,
|
|
|
|
},
|
|
|
|
id: {
|
|
|
|
type: String,
|
|
|
|
default: null,
|
|
|
|
},
|
|
|
|
autocomplete: {
|
|
|
|
type: String,
|
|
|
|
default: null,
|
|
|
|
},
|
|
|
|
placeholder: {
|
|
|
|
type: String,
|
|
|
|
default: null,
|
|
|
|
},
|
|
|
|
inputmode: {
|
|
|
|
type: String,
|
|
|
|
default: null,
|
|
|
|
},
|
|
|
|
icon: {
|
|
|
|
type: String,
|
|
|
|
default: null,
|
|
|
|
},
|
|
|
|
options: {
|
|
|
|
type: [Array, Object],
|
|
|
|
default: null,
|
|
|
|
},
|
|
|
|
type: {
|
|
|
|
type: String,
|
|
|
|
default: 'text',
|
|
|
|
},
|
|
|
|
isReadOnly: {
|
|
|
|
type: Boolean,
|
|
|
|
default: false,
|
|
|
|
},
|
|
|
|
modelValue: {
|
|
|
|
type: [String, Number, Boolean, Array, Object],
|
|
|
|
default: '',
|
|
|
|
},
|
2024-07-26 12:51:57 +00:00
|
|
|
showCharCount: {
|
|
|
|
type: Boolean,
|
|
|
|
default: false,
|
|
|
|
},
|
|
|
|
maxInputLength: {
|
|
|
|
type: Number,
|
|
|
|
default: null,
|
|
|
|
},
|
2024-09-16 15:59:46 +00:00
|
|
|
extraHigh: {
|
|
|
|
type: Boolean,
|
|
|
|
default: false,
|
|
|
|
},
|
2023-03-17 15:13:37 +00:00
|
|
|
required: Boolean,
|
|
|
|
borderless: Boolean,
|
|
|
|
transparent: Boolean,
|
|
|
|
ctrlKFocus: Boolean,
|
2023-03-03 15:54:28 +00:00
|
|
|
});
|
|
|
|
const emit = defineEmits(['update:modelValue', 'setRef']);
|
|
|
|
const computedValue = computed({
|
2023-03-17 15:13:37 +00:00
|
|
|
get: () => props.modelValue,
|
|
|
|
set: (value) => {
|
|
|
|
emit('update:modelValue', value);
|
|
|
|
},
|
2023-03-03 15:54:28 +00:00
|
|
|
});
|
|
|
|
const inputElClass = computed(() => {
|
2023-03-17 15:13:37 +00:00
|
|
|
const base = [
|
|
|
|
'px-3 py-2 max-w-full focus:ring focus:outline-none border-gray-700 rounded w-full',
|
|
|
|
'dark:placeholder-gray-400',
|
2024-09-16 15:59:46 +00:00
|
|
|
props.extraHigh ? 'h-80' : (computedType.value === 'textarea' ? 'h-44' : 'h-12'),
|
2023-03-17 15:13:37 +00:00
|
|
|
props.borderless ? 'border-0' : 'border',
|
|
|
|
// props.transparent && !props.isReadOnly ? 'bg-transparent' : 'bg-white dark:bg-slate-800',
|
|
|
|
props.isReadOnly ? 'bg-gray-50 dark:bg-slate-600' : 'bg-white dark:bg-slate-800',
|
|
|
|
];
|
|
|
|
if (props.icon) {
|
|
|
|
base.push('pl-10');
|
|
|
|
}
|
|
|
|
return base;
|
2023-03-03 15:54:28 +00:00
|
|
|
});
|
|
|
|
const computedType = computed(() => (props.options ? 'select' : props.type));
|
|
|
|
const controlIconH = computed(() => (props.type === 'textarea' ? 'h-full' : 'h-12'));
|
|
|
|
const mainService = MainService();
|
|
|
|
const selectEl = ref(null);
|
|
|
|
const textareaEl = ref(null);
|
|
|
|
const inputEl = ref(null);
|
|
|
|
onMounted(() => {
|
2023-03-17 15:13:37 +00:00
|
|
|
if (selectEl.value) {
|
|
|
|
emit('setRef', selectEl.value);
|
|
|
|
} else if (textareaEl.value) {
|
|
|
|
emit('setRef', textareaEl.value);
|
|
|
|
} else {
|
|
|
|
emit('setRef', inputEl.value);
|
|
|
|
}
|
2023-03-03 15:54:28 +00:00
|
|
|
});
|
|
|
|
if (props.ctrlKFocus) {
|
2023-03-17 15:13:37 +00:00
|
|
|
const fieldFocusHook = (e) => {
|
|
|
|
if (e.ctrlKey && e.key === 'k') {
|
|
|
|
e.preventDefault();
|
|
|
|
inputEl.value.focus();
|
|
|
|
} else if (e.key === 'Escape') {
|
|
|
|
inputEl.value.blur();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
onMounted(() => {
|
|
|
|
if (!mainService.isFieldFocusRegistered) {
|
|
|
|
window.addEventListener('keydown', fieldFocusHook);
|
|
|
|
mainService.isFieldFocusRegistered = true;
|
|
|
|
} else {
|
|
|
|
// console.error('Duplicate field focus event')
|
|
|
|
}
|
|
|
|
});
|
|
|
|
onBeforeUnmount(() => {
|
|
|
|
window.removeEventListener('keydown', fieldFocusHook);
|
|
|
|
mainService.isFieldFocusRegistered = false;
|
|
|
|
});
|
2023-03-03 15:54:28 +00:00
|
|
|
}
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<template>
|
2023-03-17 15:13:37 +00:00
|
|
|
<div class="relative">
|
2024-07-26 12:51:57 +00:00
|
|
|
<select v-if="computedType === 'select'" :id="id" v-model="computedValue" :name="name" :class="inputElClass"
|
|
|
|
:disabled="isReadOnly">
|
2023-03-17 15:13:37 +00:00
|
|
|
<option v-if="placeholder" class="text-opacity-25" value="" disabled selected>{{ placeholder }}</option>
|
2023-03-24 10:41:52 +00:00
|
|
|
<option v-for="(option, index) in options" :key="index" :value="option.value ?? index">
|
2023-03-17 15:13:37 +00:00
|
|
|
{{ option.label ?? option }}
|
|
|
|
</option>
|
|
|
|
</select>
|
2024-07-26 12:51:57 +00:00
|
|
|
<textarea v-else-if="computedType === 'textarea'" :id="id" v-model="computedValue" :class="inputElClass"
|
|
|
|
:name="name" :placeholder="placeholder" :required="required" />
|
|
|
|
<input v-else :id="id" ref="inputEl" v-model="computedValue" :name="name" :inputmode="inputmode"
|
|
|
|
:autocomplete="autocomplete" :required="required" :placeholder="placeholder" :type="computedType"
|
|
|
|
:class="inputElClass" :readonly="isReadOnly" />
|
2023-03-17 15:13:37 +00:00
|
|
|
<FormControlIcon v-if="icon" :icon="icon" :h="controlIconH" />
|
|
|
|
<slot />
|
2024-07-26 12:51:57 +00:00
|
|
|
<span v-if="showCharCount" class="message-counter" :class="{ 'text-red-500': maxInputLength && maxInputLength < computedValue.length }">
|
|
|
|
{{ computedValue.length }}
|
|
|
|
<template v-if="maxInputLength">
|
|
|
|
/ {{ maxInputLength }}
|
|
|
|
</template>
|
|
|
|
</span>
|
2023-03-17 15:13:37 +00:00
|
|
|
</div>
|
2023-03-03 15:54:28 +00:00
|
|
|
</template>
|