mirror of
https://github.com/PhotonVision/photonvision
synced 2026-06-28 02:11:40 +00:00
## Description #1900 updated how the value was handled in pv-slider, and unintentionally removed bounds protection. This restores bounds protection. Unfortunately, there is an edge case that might be rather difficult to solve. If the slider is already at the min/max, you can enter a number through the text field, and while the value won't actually update, the text field keeps the entered value, likely because the model value didn't change, and therefore, a rerender isn't triggered. However, this is an edge case that I doubt many people will actually encounter, so we should still ship this. Fixes #2221. ## Meta Merge checklist: - [x] Pull Request title is [short, imperative summary](https://cbea.ms/git-commit/) of proposed changes - [x] The description documents the _what_ and _why_ - [ ] If this PR changes behavior or adds a feature, user documentation is updated - [ ] If this PR touches photon-serde, all messages have been regenerated and hashes have not changed unexpectedly - [ ] If this PR touches configuration, this is backwards compatible with settings back to v2025.3.2 - [ ] If this PR touches pipeline settings or anything related to data exchange, the frontend typing is updated - [x] If this PR addresses a bug, a regression test for it is added
88 lines
2.3 KiB
Vue
88 lines
2.3 KiB
Vue
<script setup lang="ts">
|
|
import TooltippedLabel from "@/components/common/pv-tooltipped-label.vue";
|
|
import { computed } from "vue";
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
label?: string;
|
|
tooltip?: string;
|
|
modelValue: number;
|
|
min: number;
|
|
max: number;
|
|
step?: number;
|
|
disabled?: boolean;
|
|
sliderCols?: number;
|
|
}>(),
|
|
{ step: 1, disabled: false, sliderCols: 8 }
|
|
);
|
|
const emit = defineEmits<{ (e: "update:modelValue", value: number): void }>();
|
|
|
|
// Debounce function
|
|
function debounce(func: (...args: any[]) => void, wait: number) {
|
|
let timeout: ReturnType<typeof setTimeout>;
|
|
return function (...args: any[]) {
|
|
clearTimeout(timeout);
|
|
timeout = setTimeout(() => func.apply(this, args), wait);
|
|
};
|
|
}
|
|
|
|
const debouncedEmit = debounce((v: number) => {
|
|
if (v < props.min) {
|
|
emit("update:modelValue", props.min);
|
|
} else if (v > props.max) {
|
|
emit("update:modelValue", props.max);
|
|
} else {
|
|
emit("update:modelValue", v);
|
|
}
|
|
}, 20);
|
|
|
|
const localValue = computed({
|
|
get: () => props.modelValue,
|
|
set: (v) => debouncedEmit(parseFloat(v as unknown as string))
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div class="d-flex">
|
|
<v-col :cols="12 - sliderCols" class="pl-0 pt-10px pb-10px d-flex align-center">
|
|
<tooltipped-label :tooltip="tooltip" :label="label" />
|
|
</v-col>
|
|
<v-col :cols="sliderCols - 1" class="pl-0 pt-10px pb-10px">
|
|
<v-slider
|
|
v-model="localValue"
|
|
class="align-center"
|
|
:max="max"
|
|
:min="min"
|
|
hide-details
|
|
color="primary"
|
|
:disabled="disabled"
|
|
:step="step"
|
|
append-icon="mdi-menu-right"
|
|
prepend-icon="mdi-menu-left"
|
|
@click:append="localValue += step"
|
|
@click:prepend="localValue -= step"
|
|
/>
|
|
</v-col>
|
|
<v-col :cols="1" class="pr-0 pt-10px pb-10px">
|
|
<v-text-field
|
|
:model-value="localValue"
|
|
color="primary"
|
|
:max="max"
|
|
:min="min"
|
|
:disabled="disabled"
|
|
class="mt-0 pt-0"
|
|
density="compact"
|
|
hide-details
|
|
single-line
|
|
type="number"
|
|
variant="underlined"
|
|
style="width: 100%"
|
|
:step="step"
|
|
:hide-spin-buttons="true"
|
|
@keyup.enter="localValue = $event.target.value"
|
|
@blur="localValue = $event.target.value"
|
|
/>
|
|
</v-col>
|
|
</div>
|
|
</template>
|