Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tag are removed on dropdown close #1430

Open
3 tasks done
balex25 opened this issue Jan 7, 2025 · 3 comments
Open
3 tasks done

Tag are removed on dropdown close #1430

balex25 opened this issue Jan 7, 2025 · 3 comments

Comments

@balex25
Copy link

balex25 commented Jan 7, 2025

Prerequisites

  • I am running the latest version
  • I checked the documentation and found no answer
  • I checked to make sure that this issue has not already been filed

Hello, when I open the dropdown on single value mode and do not select any value dropdown it closes, but my value is removed too.

After some testing, I see the problem comes from the templates tag, now my template is:

      // Tag template
      tag: function (tagData, tagify) {

        console.log(tagData);

        // Safely handle an undefined tagData.name
        const displayName = tagData.name || tagData.value;

        let mediaIcon = tagData.icon
        ? `<img src="${tagData.icon}" alt="${displayName}" class="tag-avatar">`
        : tagData.emoji
        ? `<span class="emoji">${tagData.emoji}</span>`
        : `<span class="noicon">${displayName.charAt(0) || "?"}</span>`;
      
        return `
          <tag title="${tagData.title || tagData.value}"
               contenteditable='false'
               spellcheck='false'
               tabIndex="${this.settings.a11y.focusableTags ? 0 : -1}"
               class="${this.settings.classNames.tag} ${tagData.class ? tagData.class : ""}"
               ${this.getAttributes(tagData)}>
            <x title='remove' class="${this.settings.classNames.tagX}" role='button' aria-label='remove tag'></x>
            <div>
              ${mediaIcon}
              <span class="${this.settings.classNames.tagText}">${tagData.name ?? tagData.value}</span>
            </div>
          </tag>
        `;
      },

If I use the demo code from "Example of overriding the tag template", all seem to work correctly, and the tag is not removed when the dropdown is open/closed with no value selected.

My template:
chrome_YwDT3CnhIk

Example of overriding the tag template (all seem to work correctly):
chrome_5FFP0m6uVA

This line seems to cause the problem, but I don't know why, as all are generated correct:

        let mediaIcon = tagData.icon
        ? `<img src="${tagData.icon}" alt="${displayName}" class="tag-avatar">`
        : tagData.emoji
        ? `<span class="emoji">${tagData.emoji}</span>`
        : `<span class="noicon">${displayName.charAt(0) || "?"}</span>`;

Also, here is the full code that generates Tagify instances.

function createTagify(inputElement, {
  whitelist       = [],
  enforceWhitelist= false,
  placeholder     = 'Select...',
  dropdownEnabled = 0,
  searchKeys      = ['value', 'name'],
} = {}) {

  const baseConfig = {
    whitelist,
    enforceWhitelist,
    maxTags: 10,
    editTags: false,
    createInvalidTags: false,
    skipInvalid: true,
    pasteAsTags: false,
    mode: 'select',
    placeholder,
    dropdown: {
      enabled: dropdownEnabled, // 0 means “always show on focus” if there's any whitelist
      maxItems: 10,
      closeOnSelect: true,
      highlightFirst: true,
      searchKeys: searchKeys,
      classname: 'credits-tagify__dropdown',
    },
    templates: {

      // Tag template
      /*
      tag: function (tagData, tagify) {

        console.log(tagData);

        // Safely handle an undefined tagData.name
        const displayName = tagData.name || tagData.value;

        let mediaIcon = tagData.icon
        ? `<img src="${tagData.icon}" alt="${displayName}" class="tag-avatar">`
        : tagData.emoji
        ? `<span class="emoji">${tagData.emoji}</span>`
        : `<span class="noicon">${displayName.charAt(0) || "?"}</span>`;
      
        return `
          <tag title="${tagData.title || tagData.value}"
               contenteditable='false'
               spellcheck='false'
               tabIndex="${this.settings.a11y.focusableTags ? 0 : -1}"
               class="${this.settings.classNames.tag} ${tagData.class ? tagData.class : ""}"
               ${this.getAttributes(tagData)}>
            <x title='remove' class="${this.settings.classNames.tagX}" role='button' aria-label='remove tag'></x>
            <div>
              ${mediaIcon}
              <span class="${this.settings.classNames.tagText}">${tagData.name ?? tagData.value}</span>
            </div>
          </tag>
        `;
      },
      */

      tag: function (tagData, tagify) {
        return `<tag title="${(tagData.title || tagData.value)}"
                  contenteditable='false'
                  spellcheck='false'
                  tabIndex="${this.settings.a11y.focusableTags ? 0 : -1}"
                  class="${this.settings.classNames.tag} ${tagData.class ? tagData.class : ""}"
                  ${this.getAttributes(tagData)}>
          <x title='' class="${this.settings.classNames.tagX}" role='button' aria-label='remove tag'></x>
          <div>
              <span class="${this.settings.classNames.tagText}">${tagData[this.settings.tagTextProp] || tagData.value}</span>
          </div>
        </tag>`;
      },
    
      // Dropdown item template
      dropdownItem: function (item) {
        let avatarHtml = item.icon
          ? `<div class="avatar"><img src="${item.icon}"></div>`
          : item.emoji
          ? `<div class="avatar"><span>${item.emoji}</span></div>`
          : `<div class="avatar"><span>${item.name.charAt(0)}</span></div>`;

        // Example: if item has "suggested" property, we show an extra line
        let usernameHtml = item.suggested
          ? `<div class="username">Suggested</div>`
          : '';

        return `
          <div ${this.getAttributes(item)}
               class='${this.settings.classNames.dropdownItem} ${item.class || ""}'
               tabindex="0"
               role="option">
            <div class="user">
                ${avatarHtml}
                <div class="info">
                    <div class="name">${item.name}</div>
                    ${usernameHtml}
                </div>
            </div>
          </div>
        `;
      }
    }
  };

  return new Tagify(inputElement, baseConfig);
}

Update:
After some more tests, I see if I put any HTML tag with content, like <span>text</span>, it will delete the currently selected tag on the dropdown open/close.

Ex:

      tag: function (tagData, tagify) {
        return `
        <tag title="${(tagData.title || tagData.value)}"
          contenteditable='false'
          spellcheck='false'
          tabIndex="${this.settings.a11y.focusableTags ? 0 : -1}"
          class="${this.settings.classNames.tag} ${tagData.class ? tagData.class : ""}"
          ${this.getAttributes(tagData)}>
          <x title='' class="${this.settings.classNames.tagX}" role='button' aria-label='remove tag'></x>
          <div>
            <span>test</span>
            <span class="${this.settings.classNames.tagText}">${tagData.name || tagData.value}</span>
          </div>
        </tag>`;
      },
@balex25
Copy link
Author

balex25 commented Jan 7, 2025

One solution I have found so far is:

      tag: function (tagData) {

        console.log('tagData', tagData);

        const display = tagData.name || tagData.value;
        let mediaIcon = '';

        if(tagData.icon && tagData.icon !== '') {
          mediaIcon = `<img src="${tagData.icon}" alt="${tagData.name}" class="tag-avatar">`;
        } else if (tagData.emoji && tagData.emoji !== '') {
          mediaIcon = `<span class="emoji" data-emoji="${tagData.emoji}"></span>`;
        } else {
          mediaIcon = `<span class="noicon" data-letter="${display.charAt(0)}"></span>`;
        }

        return `
          <tag title="${tagData.value}"
               contenteditable='false'
               spellcheck='false'
               tabIndex="-1"
               class="${this.settings.classNames.tag} ${tagData.class || ""} ${tagData.value}"
               ${this.getAttributes(tagData)}>
            <x title='remove' tabindex='-1' class="${this.settings.classNames.tagX}" role='button' aria-label='remove'></x>
            <div>
              ${mediaIcon}
              <span class="${this.settings.classNames.tagText}">
                ${tagData.name ?? tagData.value}
              </span>
            </div>
          </tag>
        `;
      },

Render content of custom HTML elements as an attribute and use CSS to render like as:

.tagify--cat .emoji::after {
	content: attr(data-emoji);
	/*line-height: 1;*/
	font-size: 100%;
	width: 16px;
	height: 16px;
	vertical-align: middle;
	display: inline-block;
}

.tagify--cat .noicon:after {
	content: attr(data-letter);
	font-size: 11px;
  font-weight: bold;
  line-height: 1;
}

Interestingly, the IMG tag seems to work fine, but other elements, such as DIV or SPAN, are not working. Maybe it is a sanitization problem or a bug.

@yairEO
Copy link
Owner

yairEO commented Jan 8, 2025

Can you please create a minimal demo of the bug by cloning this jsbin?

https://jsbin.com/jekuqap/edit?html,js,output

@yairEO
Copy link
Owner

yairEO commented Jan 10, 2025

Your tag template deviates considerably from the official template. Why did you stripped away so many important things from it? I'm not surprised things aren't working.

The internal span element is missing the attribute "contenteditable='true'"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants