Files
PhotonVision/photon-client/src/components/common/pv-slider.vue
Gold856 d44d9fbbeb Prevent slider from going past bounds (#2222)
## 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
2025-12-07 09:49:14 -08:00

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>