ui_elements_pills.mjs

/**
## /ui/elements/pills

Exports the pills(component) method as mapp.ui.elements.pills() to create a pills ui component.

@module /ui/elements/pills
*/

mapp.utils.merge(mapp.dictionaries, {
  en: {
    pill_component_remove: 'Remove'
  }
});

/**
@function pills

@description
Decorates the component object with add() and remove() methods before returning the component.

@param {Object} component 
The component object to be decorated.

@property {HTMLElement} component.target 
The target element to append to.
@property {Object} [component.pills]
array of values to be initially selected
@property {Function} [component.addCallback] 
Function to execute once a pill has been added. Takes as arguments added value and array of selected values.
@property {Function} [component.removeCallback] 
Function to execute once a pill has been removed. Takes as arguments removed value and array of selected values.

@returns {Object} 
The decorated component object.
*/
export default function pills(component = {}) {

  component.container = mapp.utils.html.node`<div class="pill-container">`

  // Create set from pills array.
  component.pills = Array.isArray(component.pills) ? new Set(component.pills) : new Set();

  component.add = add

  component.remove = remove

  // create pills for values passed initially
  component.pills.forEach(pill => component.add(pill));

  // Append container to component target if instanceof HTMLElement
  if (component.target instanceof HTMLElement) {
    component.target.append(component.container);
  };

  return component;
};

/**
@function add

@description
Adds a pill with the provided value to the pill component (this) and executes the component.addCallback() method if declared.

@param {string} val The pill value.
*/
function add(val) {

  const component = this;

  const pill = mapp.utils.html.node`<div
    class="pill"
    style=${component.css_pill}
    data-value=${val}
    title="${val}">${val}`

  // Only append the remove button to pill if configured.
  if (component.removeCallback) {
    pill.append(mapp.utils.html.node`<button
      data-value=${val}
      title=${mapp.dictionary.pill_component_remove}
      class="primary-background"
      onclick=${e => {
        e.stopPropagation();
        component.remove(val)
      }}>&#10005;`)
  }

  if (!component.pills.has(val)) {
    component.pills.add(val);
  }

  // add pill
  component.container.append(pill);

  if (typeof component.addCallback === 'function') {

    // execute add callback if exists
    component.addCallback(val, component.pills);
  }
}

/**
@function remove

@description
Removes a pill with the provided value to the pill component (this) and executes the component.removeCallback() method if declared.

@param {string} val The pill value.
*/
function remove(val) {

  const component = this;

  const pillElement = Array.from(component.container.children)
    .find(child => child.getAttribute('data-value') === val.toString())

  pillElement?.remove()

  // remove pill from selection
  component.pills.delete(val);

  // execute removeCallback if defined
  if (typeof component.removeCallback === 'function') {
    component.removeCallback(val, component.pills);
  }
}