ui_elements_toast.mjs

/**
### /ui/elements/toast

Exports the toast element method as mapp.ui.elements.toast()

@module /ui/elements/toast
*/

/**
@function toast

@description
The toast method creates and appends a toast HTMLElement to the document body.

The method returns a promise which will resolve to the value of a button element defined in the actions array.

```js
const toast = mapp.ui.elements.toast({
	logo: "https://geolytix.github.io/public/mapp_v4/emblem.svg",
	content: 'Hello from ui toast!',
	actions: [{
		label: 'Accept',
		value: 'true',
		classlist: 'bold raised accept',
		callback: (e, params) => console.log(e)
	  },
	  {
		label: 'Reject',
		classlist: 'bold raised reject',
		value: 'false',
		callback: (e, params) => console.log(e)
	  }]
});
```

Without any actions provided the toast element will disappear after a timeout params in ms.

@param {Object} params Params for the toast element.
@property {array} [params.actions] An array of action objects to generate toast element buttons.
@property {string} [params.data_id='ui-elements-toast'] The data-id attribute value for the element.
@property {string} [params.logo] A src string for a logo element.
@property {string} [params.timeout=3000] A toast with out action elements will disappear after the timeout in ms.
@property {boolean} [params.close] An optional button to close the toast will be added to the toast element.
@property {string} [params.accept] Shorthand for accept button. String value will be used for the label.
@property {string} [params.cancel] Shorthand for cancel button. String value will be used for the label.
@property {string} [params.classlist] list of classes to apply on the button, optional. Example: "raised bold accept".
@property {HTMLElement} [params.content] HTML to display in the toast container. Can be text with links etc. Defaults to generic text.
@returns {Promise} The promise will resolve to the value of the button element.
*/
export default function toast(params = {}) {
  params.data_id ??= 'ui-elements-toast';
  params.content ??= 'Set custom HTML content in element configuration.';

  params.actions ??= [];

  if (params.accept) {
    params.actions.push({
      label: typeof params.accept === 'string' ? params.accept : 'Accept',
    });
  }

  if (params.cancel) {
    params.actions.push({
      label: typeof params.accept === 'string' ? params.cancel : 'Cancel',
    });
  }

  return new Promise((resolve) => {
    let closeBtn;
    if (params.close) {
      closeBtn = mapp.utils.html`
        <button class="material-symbols-outlined close"
          onclick=${hide_toast}>`;
    }

    const actions = params.actions?.map((action) => {
      // default styling if unset
      action.classlist ??= 'bold raised primary';
      // Use label property as default for value.
      action.value ??= action.label;
      return mapp.utils.html`<button class="${action.classlist}"
        value=${action.value}
        onclick=${(e) => hide_toast(e, action)}>${action.label}`;
    });

    if (!actions.length) {
      params.timeout ??= 3000;

      setTimeout(hide_toast, params.timeout);
    }

    const logo =
      params.logo &&
      mapp.utils.html`<div class="toast-logo"><img src=${params.logo}>`;

    const container = mapp.utils.html.node`
      <div data-id=${params.data_id} class="toast">
      ${closeBtn}
      ${logo}
      ${params.content}
      <div class="actions">
      ${actions}
      </div>`;

    document.body.append(container);

    function hide_toast(e, action) {
      container.classList.add('ease-out');

      // element removed in a timeout so that hide smooth animation can complete
      setTimeout(function () {
        container.remove();
      }, 1200);

      if (action?.callback instanceof Function) action.callback(e);

      resolve(e?.target.value);
    }
  });
}