Merge multiple ref assignments in templates
What problem does this feature solve?
When authoring headless libraries, composables or container components, it is often encountered that you pass in certain attributes to your user that must be bound to an element that the user renders himself.
For example, you can have an const { attributes } = useAriaTitle()
composable, which returns the aria attributes for headings and the user can then create his own
If we take this a step further in the context of Modals, Popovers etc, the logic behind the composable can become substantial and sometimes requires access to the rendered dom element ( for example, if you want to detect a click outside of something, you need to know the boundaries ).
Refs are an ideal candidate for that. You can add a ref
property to the attributes object that you return to the user and when he binds them, you have access to the dom element, when mounted, without requiring additional assistance on his part.
This would work great, however, Vue only allows one ref to be bound to an element in templates. So, if the user wants to add his own local ref to the same element, only one of these will work based on the ordering of attributes.
This is demonstrated here
It would be great if the compiler and Vue core allow binding multiple refs on the same element and they become merged in an array and have all of them invoked at runtime.
I will assume some hints in advance, so:
Require a ref to be passed to the composable, like this
<script>
const title = ref()
const { attributes } = useAriaTitle(title)
</script>
<template>
<h3 v-bind="attributes" ref="title" />
</template>
While this works, it would require the user to manage these manually. This pattern also breaks down in complex scenarios where the composable is called in the root component, but requires access to a ref which is located in a child component ( because the user might want to use compound components instead of a single monolithic one )
What does the proposed API look like?
I don't think this requires a public interface of some sort ( it might even be possible with render functions / JSX ), but it would be great if the attributes could be merged in a similar fashion
<script>
const regularRef = ref()
function. functionRef(element) {}
</script>
<template>
<h3 :ref="[regularRef, functionRef]">My title</h3>
</template
So, using this syntax, the DOM element will be assigned to regularRef
and the functionRef
will get called with the element.
This does not require a change in how refs function, its more of an ability to handle an array of options that it already knows how to handle.
This way, the mergeProps
handler can merge them similar to how it merges event listeners.
Use
ref
and Custom Directives.
Can you elaborate on that?
Are you suggesting that I create a custom directive and add that to the attributes
object, so that it gets bound to the element?
As far as I'm aware the withDirectives
helper works only inside render functions, is there a way to add a directive through the v-bind
syntax?
I'm looking for an unobtrusive way to accomplish this, ie if the developer does <h3 v-bind="titleAttrs"></h3>
and later he decides to add his own ref to the element, I would like that to work by default for him, instead of having to use custom directives to achieve a functionality that should be available to him by default
<script lang="ts" setup>
import { ref } from 'vue';
const regularRef = ref();
const vFunc = { // v-func
mounted(el) {
// ...
}.
};
</script>
<template>
<h3 ref="regularRef" v-func>My title</h3>
</template
<script lang="ts" setup> import { ref } from 'vue'; const regularRef = ref(); const vFunc = { // v-func mounted(el) { // ... }. }; </script> <template> <h3 ref="regularRef" v-func>My title</h3> </template
Thank you for that example. Unfortunately, this doesn't quite satisfy the initial requirements of not having to do anything extra besides binding the attributes
object to the element and adds additional properties.
It would have been cool if it was possible to add directives through the v-bind="attributes"
syntax, where the directive could have been an implementation detail.
I would still prefer the manual binding as opposed to this, because at least its clear in what it does, but it would have been cool if this was handled by the framework