fix(frontend): MkSelectでmodelValueが更新されない限り値を更新しないように

This commit is contained in:
syuilo 2024-10-21 19:14:02 +09:00
parent 9d0f7eeb9c
commit c4f1ca2fd9

View file

@ -16,9 +16,8 @@ SPDX-License-Identifier: AGPL-3.0-only
@keydown.space.enter="show" @keydown.space.enter="show"
> >
<div ref="prefixEl" :class="$style.prefix"><slot name="prefix"></slot></div> <div ref="prefixEl" :class="$style.prefix"><slot name="prefix"></slot></div>
<select <div
ref="inputEl" ref="inputEl"
v-model="v"
v-adaptive-border v-adaptive-border
tabindex="-1" tabindex="-1"
:class="$style.inputCore" :class="$style.inputCore"
@ -26,27 +25,25 @@ SPDX-License-Identifier: AGPL-3.0-only
:required="required" :required="required"
:readonly="readonly" :readonly="readonly"
:placeholder="placeholder" :placeholder="placeholder"
@input="onInput"
@mousedown.prevent="() => {}" @mousedown.prevent="() => {}"
@keydown.prevent="() => {}" @keydown.prevent="() => {}"
> >
<div style="pointer-events: none;">{{ currentValueText ?? '' }}</div>
<div style="display: none;">
<slot></slot> <slot></slot>
</select> </div>
</div>
<div ref="suffixEl" :class="$style.suffix"><i class="ti ti-chevron-down" :class="[$style.chevron, { [$style.chevronOpening]: opening }]"></i></div> <div ref="suffixEl" :class="$style.suffix"><i class="ti ti-chevron-down" :class="[$style.chevron, { [$style.chevronOpening]: opening }]"></i></div>
</div> </div>
<div :class="$style.caption"><slot name="caption"></slot></div> <div :class="$style.caption"><slot name="caption"></slot></div>
<MkButton v-if="manualSave && changed" primary :class="$style.save" @click="updated"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, nextTick, ref, watch, computed, toRefs, VNode, useSlots, VNodeChild } from 'vue'; import { onMounted, nextTick, ref, watch, computed, toRefs, VNode, useSlots, VNodeChild } from 'vue';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js';
import { useInterval } from '@@/js/use-interval.js'; import { useInterval } from '@@/js/use-interval.js';
import { i18n } from '@/i18n.js';
import type { MenuItem } from '@/types/menu.js'; import type { MenuItem } from '@/types/menu.js';
import * as os from '@/os.js';
const props = defineProps<{ const props = defineProps<{
modelValue: string | null; modelValue: string | null;
@ -56,25 +53,20 @@ const props = defineProps<{
placeholder?: string; placeholder?: string;
autofocus?: boolean; autofocus?: boolean;
inline?: boolean; inline?: boolean;
manualSave?: boolean;
small?: boolean; small?: boolean;
large?: boolean; large?: boolean;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
(ev: 'changeByUser', value: string | null): void; (ev: 'update:modelValue', value: string | number | null): void;
(ev: 'update:modelValue', value: string | null): void;
}>(); }>();
const slots = useSlots(); const slots = useSlots();
const { modelValue, autofocus } = toRefs(props); const { modelValue, autofocus } = toRefs(props);
const v = ref(modelValue.value);
const focused = ref(false); const focused = ref(false);
const opening = ref(false); const opening = ref(false);
const changed = ref(false); const currentValueText = ref<string | null>(null);
const invalid = ref(false);
const filled = computed(() => v.value !== '' && v.value != null);
const inputEl = ref<HTMLObjectElement | null>(null); const inputEl = ref<HTMLObjectElement | null>(null);
const prefixEl = ref<HTMLElement | null>(null); const prefixEl = ref<HTMLElement | null>(null);
const suffixEl = ref<HTMLElement | null>(null); const suffixEl = ref<HTMLElement | null>(null);
@ -85,26 +77,6 @@ const height =
36; 36;
const focus = () => container.value?.focus(); const focus = () => container.value?.focus();
const onInput = (ev) => {
changed.value = true;
};
const updated = () => {
changed.value = false;
emit('update:modelValue', v.value);
};
watch(modelValue, newValue => {
v.value = newValue;
});
watch(v, () => {
if (!props.manualSave) {
updated();
}
invalid.value = inputEl.value?.validity.badInput ?? true;
});
// //
// 0 // 0
@ -134,6 +106,31 @@ onMounted(() => {
}); });
}); });
watch(modelValue, () => {
const scanOptions = (options: VNodeChild[]) => {
for (const vnode of options) {
if (typeof vnode !== 'object' || vnode === null || Array.isArray(vnode)) continue;
if (vnode.type === 'optgroup') {
const optgroup = vnode;
if (Array.isArray(optgroup.children)) scanOptions(optgroup.children);
} else if (Array.isArray(vnode.children)) { //
const fragment = vnode;
if (Array.isArray(fragment.children)) scanOptions(fragment.children);
} else if (vnode.props == null) { // v-if false
// nop?
} else {
const option = vnode;
if (option.props?.value === modelValue.value) {
currentValueText.value = option.children as string;
break;
}
}
}
};
scanOptions(slots.default!());
}, { immediate: true });
function show() { function show() {
if (opening.value) return; if (opening.value) return;
focus(); focus();
@ -146,11 +143,9 @@ function show() {
const pushOption = (option: VNode) => { const pushOption = (option: VNode) => {
menu.push({ menu.push({
text: option.children as string, text: option.children as string,
active: computed(() => v.value === option.props?.value), active: computed(() => modelValue.value === option.props?.value),
action: () => { action: () => {
v.value = option.props?.value; emit('update:modelValue', option.props?.value);
changed.value = true;
emit('changeByUser', v.value);
}, },
}); });
}; };
@ -248,7 +243,8 @@ function show() {
.inputCore { .inputCore {
appearance: none; appearance: none;
-webkit-appearance: none; -webkit-appearance: none;
display: block; display: flex;
align-items: center;
height: v-bind("height + 'px'"); height: v-bind("height + 'px'");
width: 100%; width: 100%;
margin: 0; margin: 0;