vuejs / core

🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
https://vuejs.org/
MIT License
47.71k stars 8.34k forks source link

Attribute text on a html tag overrides inner content #12211

Open silencerspirit opened 1 month ago

silencerspirit commented 1 month ago

Vue version

3.5.12

Link to minimal reproduction

https://stackblitz.com/edit/vitejs-vite-v477mx?file=src%2FApp.vue

Steps to reproduce

Add to html tag a attribute text

What is expected?

attribute text does not override content html tag a

What is actually happening?

attribute text overrides content html tag a

System Info

No response

Any additional comments?

No response

DENCREAT commented 1 month ago

same issue on 3.4.7

jh-leong commented 1 month ago

Thanks for reporting this.

The issue occurs because Vue sets the text as a property on the <a> element, and while <a> doesn't have a text attribute, it does have a text property via HTMLAnchorElement(https://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement/text). This causes the property to override the inner content.

As a workaround, you can use ^text to prevent Vue from setting text as a prop. This will ensure that the text value is treated as an attribute instead.

silencerspirit commented 1 month ago

Thanks for the lightning-fast response and fix! 🤝

DENCREAT commented 1 month ago

Thank you for the clarification.

To explain my use case a bit more, I'm encountering the issue with overriding inner content when using v-bind in combination with our CMS component, which maps its own fields to an HTML element (e.g., for a banner).

Here’s an example of what I'm working with:

const cmsBanner = {
  "id": "ui_banner_1",
  "text": "Some description",
  "target": "_blank",
  "href": "/some-href",
};
<a v-bind="cmsBanner">
  <img src="..." />
</a>

In this scenario, I’m not sure how the ^text solution would apply or help, since the text is part of the CMS data structure. Could you provide further clarification on how I might implement ^text in this case, or suggest an alternative approach to prevent the text property from overriding the inner content of the anchor tag?

Thank you in advance!

DENCREAT commented 1 month ago

@jh-leong thanks a lot for the quick fix

jh-leong commented 1 month ago

In this scenario, I’m not sure how the ^text solution would apply or help, since the text is part of the CMS data structure. Could you provide further clarification on how I might implement ^text in this case, or suggest an alternative approach to prevent the text property from overriding the inner content of the anchor tag?

If the text property isn't needed, you can remove it before binding like this:

const cmsBanner = {
  id: 'ui_banner_1',
  text: 'Some description',
  target: '_blank',
  href: '/some-href',
};
const obj = { ...cmsBanner };
delete obj.text;

Playground

tomtheisen commented 1 month ago

In this scenario, I’m not sure how the ^text solution would apply or help, since the text is part of the CMS data structure. Could you provide further clarification on how I might implement ^text in this case, or suggest an alternative approach to prevent the text property from overriding the inner content of the anchor tag?

If the text property isn't needed, you can remove it before binding like this:

const cmsBanner = {
  id: 'ui_banner_1',
  text: 'Some description',
  target: '_blank',
  href: '/some-href',
};
const obj = { ...cmsBanner };
delete obj.text;

Playground

You can also do it this way to avoid the delete.

const { text, ...obj } = cmsBanner;
DENCREAT commented 5 days ago

In this scenario, I’m not sure how the ^text solution would apply or help, since the text is part of the CMS data structure. Could you provide further clarification on how I might implement ^text in this case, or suggest an alternative approach to prevent the text property from overriding the inner content of the anchor tag?

If the text property isn't needed, you can remove it before binding like this:

const cmsBanner = {
  id: 'ui_banner_1',
  text: 'Some description',
  target: '_blank',
  href: '/some-href',
};
const obj = { ...cmsBanner };
delete obj.text;

Playground

You can also do it this way to avoid the delete.

const { text, ...obj } = cmsBanner;

Sure. I've done it this way. However, it's just a workaround and not a solution.

tomtheisen commented 4 days ago

Sure. I've done it this way. However, it's just a workaround and not a solution.

I don't think it's a workaround because I don't think there's a bug here. <a> is the markup representation of HTMLAnchorElement, which has a text property. Setting that changes the contents of the anchor. Everything here seems to be working as documented. Why do you expect it to work differently?

DENCREAT commented 4 days ago

Sure. I've done it this way. However, it's just a workaround and not a solution.

I don't think it's a workaround because I don't think there's a bug here. <a> is the markup representation of HTMLAnchorElement, which has a text property. Setting that changes the contents of the anchor. Everything here seems to be working as documented. Why do you expect it to work differently?

Thank you for addressing this property. I had initially assumed it would behave slightly differently. For instance, if I wanted to set the text content of a link using JavaScript, I might use something like this:

document.querySelector('a').text = 'Some text content';

Here, text is a synonym for textContent, making it a straightforward way to update the text of a link.

However, in this particular case, text is not treated as a property in Vue but rather as an attribute. To verify this behavior, I tested adding the text attribute directly to an <a> tag in plain HTML (without Vue), and it worked as expected. You can find a demonstration of this behavior in the example below:

CodePen Example

This highlights a distinction between how Vue handles attributes and properties for DOM elements.

tomtheisen commented 3 days ago

This is a difference between vue templates and HTML. <a text> is not special. <input type="checkbox" indeterminate> works in a vue template. There's no way to achieve that at all in HTML without javascript.

I don't know where or if this distinction is mentioned in the docs, but this might be working as intended.