Snapshot: alpha-2026-9
This commit is contained in:
parent
9acf5a97e2
commit
5accd5f3eb
242 changed files with 85549 additions and 2409 deletions
204
frontend/src/components/inputs/BadgeSelectField.vue
Normal file
204
frontend/src/components/inputs/BadgeSelectField.vue
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
<template>
|
||||
<div class="input-group">
|
||||
<div class="badge-list form-control form-control-lg" style="position: relative;">
|
||||
<span v-for="(option, index) in value" :key="index">
|
||||
<slot :option="option" :index="index"></slot>
|
||||
</span>
|
||||
<input type="text" class="invisible-input" v-model="textinput" ref="input"
|
||||
@keydown="keyDown">
|
||||
<ul class="dropdown-menu" style="position: absolute; top: 100%; left: 0; width: 100%; z-index: 100;"
|
||||
v-if="filteredOption.length > 0">
|
||||
<li v-for="(option, index) in filteredOption" :key="index"
|
||||
@mousedown="chooseOption(option)" @mouseover="selectOption(option)"
|
||||
:class="{chosen: localValue.includes(option), selected: selectedTag === option, 'dropdown-item': true}">
|
||||
{{ option }}
|
||||
<b>
|
||||
<b-icon-check class="checkmark"></b-icon-check>
|
||||
<b-icon-x class="cross"></b-icon-x>
|
||||
<b-icon-plus class="plus"></b-icon-plus>
|
||||
</b>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.badge-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.3rem;
|
||||
padding: 0 .25rem;
|
||||
}
|
||||
|
||||
.badge-list > * {
|
||||
flex: 0 1 auto;
|
||||
padding: .25rem 0;
|
||||
}
|
||||
|
||||
.badge-list > input.invisible-input {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
input.invisible-input {
|
||||
border: none;
|
||||
outline: none;
|
||||
color: #495057;
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right .7rem center;
|
||||
background-size: 16px 12px;
|
||||
}
|
||||
|
||||
.invisible-input:focus ~ .dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
display: none;
|
||||
overflow-x: auto;
|
||||
max-height: 50vh;
|
||||
}
|
||||
|
||||
.checkmark, .cross, .plus {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dropdown-item.selected {
|
||||
background-color: var(--bs-primary);
|
||||
color: var(--bs-light);
|
||||
|
||||
& .plus {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-item.chosen {
|
||||
& .checkmark {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-item.chosen.selected {
|
||||
background-color: var(--bs-danger);
|
||||
color: var(--bs-light);
|
||||
|
||||
& .cross {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
& .checkmark, & .plus {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import * as BIcons from "bootstrap-icons-vue";
|
||||
|
||||
export default {
|
||||
name: "BadgeSelectField",
|
||||
data() {
|
||||
return {
|
||||
textinput: "",
|
||||
selectedTag: ""
|
||||
}
|
||||
},
|
||||
components: {
|
||||
...BIcons
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: "value",
|
||||
event: "addElement"
|
||||
},
|
||||
computed: {
|
||||
availableTags() {
|
||||
return this.options.filter(option => !this.localValue.includes(option));
|
||||
},
|
||||
filteredOption() {
|
||||
return this.options.filter(option => option.includes(this.textinput) || option === this.selectedTag);
|
||||
},
|
||||
localValue: {
|
||||
get() {
|
||||
return this.value;
|
||||
},
|
||||
set(value) {
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
chooseOption(option) {
|
||||
this.$emit("addElement", option);
|
||||
},
|
||||
resetSelection() {
|
||||
this.textinput = "";
|
||||
this.selectedTag = null;
|
||||
},
|
||||
selectOption(option) {
|
||||
this.selectedTag = option;
|
||||
},
|
||||
keyDown(event) {
|
||||
const key = event.key;
|
||||
if (key === "Enter") {
|
||||
event.preventDefault();
|
||||
this.chooseOption(this.selectedTag);
|
||||
this.$refs.input.blur();
|
||||
} else if (key === "ArrowDown") {
|
||||
event.preventDefault();
|
||||
if (this.selectedTag === "") {
|
||||
this.selectedTag = this.filteredOption[0];
|
||||
} else {
|
||||
const index = this.filteredOption.indexOf(this.selectedTag);
|
||||
if (index < this.filteredOption.length - 1) {
|
||||
this.selectedTag = this.filteredOption[index + 1];
|
||||
} else {
|
||||
this.selectedTag = this.filteredOption[0];
|
||||
}
|
||||
setTimeout(() => {
|
||||
const element = this.$el.querySelector(".selected");
|
||||
if (element) {
|
||||
element.scrollIntoView({block: "nearest"});
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
} else if (key === "ArrowUp") {
|
||||
event.preventDefault();
|
||||
if (this.selectedTag === "") {
|
||||
this.selectedTag = this.filteredOption[this.filteredOption.length - 1];
|
||||
} else {
|
||||
const index = this.filteredOption.indexOf(this.selectedTag);
|
||||
if (index > 0) {
|
||||
this.selectedTag = this.filteredOption[index - 1];
|
||||
} else {
|
||||
this.selectedTag = this.filteredOption[this.filteredOption.length - 1];
|
||||
}
|
||||
setTimeout(() => {
|
||||
const element = this.$el.querySelector(".selected");
|
||||
if (element) {
|
||||
element.scrollIntoView({block: "nearest"});
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
} else if (key === "ArrowRight") {
|
||||
if (event.target.selectionStart === this.textinput.length) {
|
||||
event.preventDefault();
|
||||
this.chooseOption(this.selectedTag);
|
||||
}
|
||||
} else if (key === "Escape") {
|
||||
event.preventDefault();
|
||||
this.$refs.input.blur();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Loading…
Add table
Add a link
Reference in a new issue