Open alvarosabu opened 9 months ago
I think I need to see how others handle this use case. I'll look for some other Vue examples and post back here to continue the discussion.
I need your help here, what do you think we can do? Maybe instead of an object with all the controls, let's return an array?
Here's what we have currently: return object with widgets in object.[widget name]
const state = {reset:true, helper:true}
const {reset, helper} = useControls({reset: true, helper: true})
watch(widgets, () => {
state.reset = reset.value.value
state.helper = helper.value.value
})
Here's the proposal, as I see it: return array of [widget, ...]
const state = {reset:true, helper:true}
const widgets = useControls({reset: true, helper: true})
watch(widgets, () => {
state.reset = widgets[0].value.value
state.helper = widgets[1].value.value
})
I think there'd be a tendency to get lost in the array indices. Imagine having 10 widgets and you decide to add/remove one from the middle. Everything after has to be renumbered.
Tweakpane uses .addBinding(stateObject, key, params)
.
const PARAMS = {
speed: 50,
};
const pane = new Pane();
pane.addBinding(PARAMS, 'speed', {
min: 0,
max: 100,
});
With this setup, there's no need for a watch
and so no value.value
. The value is already being "watched" with the configuration above.
A variation of Tweakpane, this time with Vue. Like Tweakpane, it uses a specific method for creating a widget and binding at the same time. This avoids the need to watch
and avoids .value.value
.
const onPaneTwoCreated = (pane: any) => {
pane.registerPlugin(CamerakitPlugin);
const PARAMS = {
flen: 55,
fnum: 1.8,
iso: 100,
};
pane.addInput(PARAMS, 'flen', {
view: 'cameraring',
series: 0,
unit: { pixels: 50, ticks: 10, value: 0.2 },
min: 1,
step: 0.02,
});
The example uses some callbacks for setup, which Leches doesn't need. Personally, I'd like to avoid having those.
In this Vuetify example, most of the config that Leches does in <setup>
is handled in <template>
. Bindings are created using v-model
in the <template>
.
<template>
<v-slider
v-model="slider"
class="align-center"
:max="max"
:min="min"
hide-details
>
<template v-slot:append>
<v-text-field
v-model="slider"
hide-details
single-line
density="compact"
type="number"
style="width: 70px"
></v-text-field>
</template>
</v-slider>
</template>
<script>
export default {
data () {
return {
min: -50,
max: 90,
slider: 40,
}
},
}
</script>
Here's a slider from UI library Quasar.
The configuration is done in the <template>
. No need for watch
.
<template>
<div class="q-pa-md">
<q-badge color="secondary">
Model: {{ value }} (-20 to 20)
</q-badge>
<q-slider
v-model="value"
:min="-20"
:max="20"
:step="4"
snap
label
color="purple"
/>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup () {
return {
value: ref(0)
}
}
}
</script>
Here's a slider example from Element UI.
Another <template>
approach with v-model
. No need for watch
.
<template>
<div class="block">
<el-slider
v-model="value"
show-input>
</el-slider>
</div>
</template>
<script>
export default {
data() {
return {
value: 0
}
}
}
</script>
My original idea was to follow the same UX as https://github.com/pmndrs/leva @andretchen0
I agree with you about using an object rather than an array, what if instead of having an object with ref values, we return a reactive object to avoid the .value
? I'm not sure that reactivity would work.
what if instead of having an object with ref values, we return a reactive object to avoid the .value? I'm not sure that reactivity would work.
Currently useControls
is returning toRefs(reactive(result))
for most of our use cases in the docs and playground, afaik.
It's my understanding that returning reactive(result)
would work for this particular use case and eliminate .value.value
.
const data = reactive({
enabled: {value: true},
frequency: {value: 1, min:0, max:10}
})
watch(data, () => console.log("fired on any change to data (deep)"));
Just to confirm, here's a Vue playground
Just FYI, if the toRefs
here is removed, we'd need to redo our demos and playground examples that use Leches. As would other users.
It will be a breaking change indeed @andretchen0 but since the package is not even in alpha it was expected. That is why I want to think it through to avoid having more breaking changes in the future.
In the case the API does change, we will need to create a ticket to update our demos and playgrounds
breaking change
Gotcha. No problem then.
I think this Leva issue points to a common use case unmet by Leva's approach and also extends to what we're talking about here: the API doesn't help you bind external state. It defaults to creating new state and then the user handles copying the new state values to the external state values.
In the case of Cientos demos, we've been doing e.g., watch
with value.value
.
From my perspective, it'd be great if we could make binding external state "just work" in Leches. In Vue, a v-model
in the <template>
could work and also give us two-way data bindings.
If we need to do it in <script>
maybe instead of useControls
returning keyed widgets – leading to this issue for Leva – it returns a builder API that can be used to further configure the Leches instance:
const myData = {enabled: false}
useControls({enabled: {label:"Enabled"}}).bind(myData)
But maybe that leads us too far away from the original intention here. In which case, no problem.
Is your feature request related to a problem? Please describe. When more than one control is passed to the
useControls
, the object returned contains all the controls refs, so to access the value inside isslider.value.value
See https://tresleches.tresjs.org/guide/controls.html#multiple-controls
Since vue reactivity uses
value
for refs and having these differences within single and multiple controls is awkward and worsens the DXDescribe the solution you'd like
@andretchen0 I need your help here, what do you think we can do? Maybe instead of an object with all the controls, let's return an array?