mirror of
https://github.com/Fabio286/antares.git
synced 2025-02-01 09:56:54 +01:00
feat(UI): ForeignKeySelect implements BaseSelect component
This commit is contained in:
parent
0043d07708
commit
302c66457d
@ -6,7 +6,7 @@
|
||||
role="combobox"
|
||||
:tabindex="searchable ? -1 : tabindex"
|
||||
@focus="activate()"
|
||||
@blur="searchable ? false : deactivate()"
|
||||
@blur="searchable ? false : handleBlurEvent()"
|
||||
@keyup.esc="deactivate()"
|
||||
@keydown.self.down.prevent="moveDown()"
|
||||
@keydown.self.up.prevent="moveUp"
|
||||
@ -23,8 +23,8 @@
|
||||
:tabindex="tabindex"
|
||||
:value="searchText"
|
||||
@input="searchText = $event.target.value"
|
||||
@focus.prevent="activate()"
|
||||
@blur.prevent="deactivate()"
|
||||
@focus.prevent="!isOpen ? activate() : false"
|
||||
@blur.prevent="handleBlurEvent()"
|
||||
@keyup.esc="deactivate()"
|
||||
@keydown.down.prevent="keyArrows('down')"
|
||||
@keydown.up.prevent="keyArrows('up')"
|
||||
@ -32,10 +32,14 @@
|
||||
>
|
||||
<span v-if="searchable && !isOpen || !searchable">{{ currentOptionLabel }}</span>
|
||||
</div>
|
||||
<div
|
||||
<Teleport
|
||||
v-if="isOpen"
|
||||
ref="teleportEl"
|
||||
:to="dropdownContainer"
|
||||
>
|
||||
<div
|
||||
ref="optionList"
|
||||
class="select__list-wrapper"
|
||||
:class="`select__list-wrapper ${dropdownClass ? dropdownClass : '' }`"
|
||||
>
|
||||
<ul class="select__list" @mousedown.prevent>
|
||||
<li
|
||||
@ -59,11 +63,12 @@
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, computed, ref, watch } from 'vue';
|
||||
import { defineComponent, computed, ref, watch, nextTick, onMounted, onUnmounted } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'BaseSelect',
|
||||
@ -71,6 +76,9 @@ export default defineComponent({
|
||||
modelValue: {
|
||||
type: [String, Number, Object]
|
||||
},
|
||||
value: {
|
||||
type: [String, Number, Object]
|
||||
},
|
||||
searchable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
@ -89,21 +97,35 @@ export default defineComponent({
|
||||
},
|
||||
optionTrackBy: {
|
||||
type: [String, Function],
|
||||
default: (opt) => opt.id || opt.value
|
||||
default: () => (opt) => {
|
||||
for (const guess of ['id', 'value']) if (opt[guess]) return guess;
|
||||
}
|
||||
},
|
||||
optionLabel: {
|
||||
type: [String, Function],
|
||||
default: (opt) => opt.label
|
||||
default: () => (opt) => opt.label ? 'label' : undefined
|
||||
},
|
||||
closeOnSelect: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
dropdownContainer: {
|
||||
type: String,
|
||||
default: '#main-content'
|
||||
},
|
||||
dropdownOffsets: {
|
||||
type: Object,
|
||||
default: () => ({ top: 10, left: 0 })
|
||||
},
|
||||
dropdownClass: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
emits: ['select', 'open', 'close', 'update:modelValue'],
|
||||
emits: ['select', 'open', 'close', 'update:modelValue', 'change', 'blur'],
|
||||
setup (props, { emit }) {
|
||||
const hightlightedIndex = ref(0);
|
||||
const isOpen = ref(false);
|
||||
const internalValue = ref(props.modelValue || props.value);
|
||||
const el = ref(null);
|
||||
const searchInput = ref(null);
|
||||
const optionList = ref(null);
|
||||
@ -134,12 +156,12 @@ export default defineComponent({
|
||||
});
|
||||
|
||||
const getOptionValue = (opt) => {
|
||||
const key = typeof props.optionTrackBy === 'function' ? props.optionTrackBy() : props.optionTrackBy;
|
||||
const key = typeof props.optionTrackBy === 'function' ? props.optionTrackBy(opt) : props.optionTrackBy;
|
||||
return key ? opt[key] : opt;
|
||||
};
|
||||
|
||||
const getOptionLabel = (opt) => {
|
||||
const key = typeof props.optionLabel === 'function' ? props.optionLabel() : props.optionLabel;
|
||||
const key = typeof props.optionLabel === 'function' ? props.optionLabel(opt) : props.optionLabel;
|
||||
return key ? opt[key] : opt;
|
||||
};
|
||||
|
||||
@ -153,19 +175,22 @@ export default defineComponent({
|
||||
});
|
||||
|
||||
const select = (opt) => {
|
||||
internalValue.value = opt;
|
||||
emit('select', opt);
|
||||
emit('update:modelValue', getOptionValue(opt));
|
||||
emit('change', opt);
|
||||
|
||||
if (props.closeOnSelect)
|
||||
deactivate();
|
||||
};
|
||||
|
||||
const isSelected = (opt) => {
|
||||
return props.modelValue === getOptionValue(opt);
|
||||
return internalValue.value === getOptionValue(opt);
|
||||
};
|
||||
|
||||
const activate = () => {
|
||||
if (isOpen.value) return;
|
||||
isOpen.value = true;
|
||||
|
||||
if (props.searchable)
|
||||
searchInput.value.focus();
|
||||
@ -173,14 +198,13 @@ export default defineComponent({
|
||||
else
|
||||
el.value.focus();
|
||||
|
||||
isOpen.value = true;
|
||||
nextTick(() => adjustListPosition());
|
||||
|
||||
emit('open');
|
||||
};
|
||||
|
||||
const deactivate = () => {
|
||||
if (!isOpen.value) return;
|
||||
|
||||
isOpen.value = false;
|
||||
|
||||
if (props.searchable)
|
||||
@ -194,6 +218,16 @@ export default defineComponent({
|
||||
emit('close');
|
||||
};
|
||||
|
||||
const adjustListPosition = () => {
|
||||
const element = el.value;
|
||||
const { left, top } = element.getBoundingClientRect();
|
||||
const { left: offsetLeft = 0, top: offsetTop = 0 } = props.dropdownOffsets;
|
||||
|
||||
optionList.value.style.left = `${left + offsetLeft}px`;
|
||||
optionList.value.style.top = `${top + element.clientHeight + offsetTop}px`;
|
||||
optionList.value.style.minWidth = `${element.clientWidth}px`;
|
||||
};
|
||||
|
||||
const keyArrows = (direction) => {
|
||||
const sum = direction === 'down' ? +1 : -1;
|
||||
const index = hightlightedIndex.value + sum;
|
||||
@ -211,6 +245,18 @@ export default defineComponent({
|
||||
optionList.value.scrollTop = optEl.offsetTop - optionList.value.clientHeight + optEl.clientHeight;
|
||||
};
|
||||
|
||||
const handleBlurEvent = () => {
|
||||
deactivate();
|
||||
emit('blur');
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', adjustListPosition);
|
||||
});
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', adjustListPosition);
|
||||
});
|
||||
|
||||
return {
|
||||
el,
|
||||
searchInput,
|
||||
@ -228,7 +274,8 @@ export default defineComponent({
|
||||
isOpen,
|
||||
hightlightedIndex,
|
||||
optionList,
|
||||
optionRefs
|
||||
optionRefs,
|
||||
handleBlurEvent
|
||||
};
|
||||
}
|
||||
});
|
||||
@ -236,7 +283,7 @@ export default defineComponent({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.select {
|
||||
position: relative;
|
||||
|
||||
display: block;
|
||||
|
||||
&__search-input {
|
||||
@ -248,10 +295,9 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
&__list-wrapper {
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
display: block;
|
||||
width: 100%;
|
||||
z-index: 50;
|
||||
z-index: 5;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
max-height: 240px;
|
||||
overflow: auto;
|
||||
|
@ -1,23 +1,15 @@
|
||||
<template>
|
||||
<select
|
||||
<BaseSelect
|
||||
ref="editField"
|
||||
:options="foreigns"
|
||||
class="form-select pl-1 pr-4"
|
||||
:class="{'small-select': size === 'small'}"
|
||||
:value="currentValue"
|
||||
dropdown-class="select-sm"
|
||||
dropdown-container=".workspace-query-results > .vscroll"
|
||||
@change="onChange"
|
||||
@blur="$emit('blur')"
|
||||
>
|
||||
<option v-if="!isValidDefault" :value="modelValue">
|
||||
{{ modelValue === null ? 'NULL' : modelValue }}
|
||||
</option>
|
||||
<option
|
||||
v-for="row in foreignList"
|
||||
:key="row.foreign_column"
|
||||
:value="row.foreign_column"
|
||||
:selected="row.foreign_column === modelValue"
|
||||
>
|
||||
{{ row.foreign_column }} {{ cutText('foreign_description' in row ? ` - ${row.foreign_description}` : '') }}
|
||||
</option>
|
||||
</select>
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@ -26,8 +18,11 @@ import Tables from '@/ipc-api/Tables';
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
import { TEXT, LONG_TEXT } from 'common/fieldTypes';
|
||||
import BaseSelect from '@/components/BaseSelect.vue';
|
||||
|
||||
export default {
|
||||
name: 'ForeignKeySelect',
|
||||
components: { BaseSelect },
|
||||
props: {
|
||||
modelValue: [String, Number],
|
||||
keyUsage: Object,
|
||||
@ -47,7 +42,8 @@ export default {
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
foreignList: []
|
||||
foreignList: [],
|
||||
currentValue: this.modelValue
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@ -55,6 +51,17 @@ export default {
|
||||
if (!this.foreignList.length) return true;
|
||||
if (this.modelValue === null) return false;
|
||||
return this.foreignList.some(foreign => foreign.foreign_column.toString() === this.modelValue.toString());
|
||||
},
|
||||
foreigns () {
|
||||
const list = [];
|
||||
|
||||
if (!this.isValidDefault)
|
||||
list.push({ value: this.modelValue, label: this.modelValue === null ? 'NULL' : this.modelValue });
|
||||
|
||||
for (const row of this.foreignList)
|
||||
list.push({ value: row.foreign_column, label: `${row.foreign_column} ${this.cutText('foreign_description' in row ? ` - ${row.foreign_description}` : '')}` });
|
||||
|
||||
return list;
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
@ -95,8 +102,8 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onChange () {
|
||||
this.$emit('update:modelValue', this.$refs.editField.value);
|
||||
onChange (opt) {
|
||||
this.$emit('update:modelValue', opt.value);
|
||||
},
|
||||
cutText (val) {
|
||||
if (typeof val !== 'string') return val;
|
||||
|
@ -56,6 +56,8 @@
|
||||
option-track-by="slug"
|
||||
option-label="name"
|
||||
class="form-select"
|
||||
dropdown-container=".workspace .connection-panel-wrapper"
|
||||
:dropdown-offsets="{top: 10}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -302,11 +302,7 @@ option:checked {
|
||||
border-color: $primary-color !important;
|
||||
@include control-shadow();
|
||||
}
|
||||
|
||||
.select__list-wrapper {
|
||||
border: 1px solid transparent;
|
||||
border-radius: $border-radius;
|
||||
box-shadow: 0px 8px 17px 0px rgba(0, 0, 0, 0.2), 0px 6px 20px 0px rgba(0, 0, 0, 0.19);
|
||||
}
|
||||
}
|
||||
|
||||
.select__list {
|
||||
@ -314,17 +310,19 @@ option:checked {
|
||||
li {
|
||||
margin: 0;
|
||||
padding: 0.3rem 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.select-sm {
|
||||
.select__list {
|
||||
li {
|
||||
.select-sm & {
|
||||
padding: 0.05rem 0.3rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.select__list-wrapper {
|
||||
border: 1px solid transparent;
|
||||
border-radius: $border-radius;
|
||||
box-shadow: 0px 8px 17px 0px rgba(0, 0, 0, 0.2), 0px 6px 20px 0px rgba(0, 0, 0, 0.19);
|
||||
}
|
||||
|
||||
.select__option--selected {
|
||||
background: rgba($primary-color, 0.25);
|
||||
}
|
||||
@ -332,8 +330,6 @@ option:checked {
|
||||
.select__option--highlight {
|
||||
background: $primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-input[type="file"] {
|
||||
overflow: hidden;
|
||||
|
@ -121,14 +121,12 @@
|
||||
border-color: $primary-color;
|
||||
}
|
||||
|
||||
.form-select {
|
||||
.select {
|
||||
&__list-wrapper {
|
||||
border-color: $bg-color-gray;
|
||||
background-color: $bg-color-light-dark;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-input[readonly] {
|
||||
background-color: $bg-color-dark;
|
||||
|
@ -18,7 +18,6 @@
|
||||
background: #ababab;
|
||||
}
|
||||
|
||||
.form-select {
|
||||
.select {
|
||||
&__list-wrapper {
|
||||
border: #bcc3ce;
|
||||
@ -29,7 +28,6 @@
|
||||
color: $light-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menu {
|
||||
.menu-item a {
|
||||
|
Loading…
x
Reference in New Issue
Block a user