PronounsPage/components/ListInput.vue
2025-06-03 13:29:40 +02:00

136 lines
4.5 KiB
Vue

<template>
<draggable
ref="draggable"
v-model="iVal"
tag="ul"
handle=".handle"
ghost-class="ghost"
class="list-unstyled"
item-key="index"
:group="group"
>
<template #item="{ element: { value, index } }">
<li>
<div>
<div class="input-group input-group-sm mb-1">
<button
v-if="!disableSorting"
:class="['btn', 'btn-light border', readonly ? '' : 'handle']"
type="button"
:aria-label="$t('table.sort')"
:disabled="readonly"
>
<Icon v="bars" />
</button>
<slot :val="value" :update="curry(update)(index)" :i="index">
<input
:value="value"
type="text"
class="form-control"
required
:readonly="readonly"
:maxlength="maxlength"
@input="(event) => update(index, event.target.value)"
>
</slot>
<button
v-if="index >= minitems"
:class="['btn', readonly ? 'btn-light border' : 'btn-outline-danger']"
type="button"
:aria-label="$t('crud.remove')"
:disabled="readonly"
@click.prevent="remove(index)"
>
<Icon v="times" />
</button>
</div>
<slot name="validation" :val="value" :i="index"></slot>
</div>
</li>
</template>
<template #footer>
<li>
<button
v-if="!readonly && (maxitems === null || iVal.length < maxitems)"
class="btn btn-outline-success w-100 btn-sm"
type="button"
:aria-label="$t('crud.add')"
@click.prevent="add"
>
<Icon v="plus" />
</button>
</li>
</template>
<li v-if="maxitems && iVal.length > maxitems" class="alert alert-danger">
<Icon v="exclamation-triangle" />
<T :params="{ maxlength: maxitems }" class="ml-1">crud.validation.listMaxLength</T>
</li>
</draggable>
</template>
<script>
import draggable from 'vuedraggable';
import { curry } from '../src/helpers.ts';
export default {
components: {
draggable,
},
props: {
modelValue: {},
prototype: { default: '', type: [String, Object] },
group: {},
readonly: { type: Boolean },
maxlength: { default: 32, type: Number },
minitems: { default: 0, type: Number },
maxitems: { default: null, type: Number },
disableSorting: { type: Boolean },
},
emits: ['update:modelValue'],
data() {
return {
curry,
};
},
computed: {
iVal: {
get() {
return this.modelValue.map((value, index) => ({ value, index }));
},
set(iVal) {
this.$emit('update:modelValue', iVal.map(({ value }) => value));
},
},
},
methods: {
remove(i) {
const v = [...this.modelValue];
v.splice(i, 1);
this.$emit('update:modelValue', v);
},
add() {
// create a deep copy of the prototype
const addedItem = JSON.parse(JSON.stringify(this.prototype));
this.$emit('update:modelValue', [...this.modelValue, addedItem]);
this.$nextTick((_) => {
const items = this.$refs.draggable.$el.querySelectorAll('[data-draggable]');
items[items.length - 1].querySelector('input,textarea,select').focus();
});
},
update(i, val) {
const v = [...this.modelValue];
v[i] = val;
this.$emit('update:modelValue', v);
},
},
};
</script>
<style lang="scss" scoped>
.ghost {
opacity: 0.5;
background: #c8ebfb;
}
</style>