Timeline animations not saving for dynamic elements added via JS

I’ve run into an issue when trying to animate dynamically injected elements. I’m able to see and edit the element in the Timeline editor, and the animation preview works — but when I click “Save”, the animation doesn’t persist.

The element in question is an info-icon (<span class="aoun-info-circle">) that gets added to specific Forminator fields via JavaScript on page load. I’m using mp-cursor-tooltip for Motion.page tooltips, which works perfectly after calling MP.refresh().

The tooltips render properly and work great during runtime. But the icon cannot be locked to a timeline: it appears in the builder, animations can be added, but cannot be saved.


What I’ve tried:

  • Confirmed mp-cursor-tooltip is present before MP.refresh()

  • Made sure the icon is in the DOM by the time the Motion.page panel opens

  • Tried setting fixed IDs or classes (aoun-info-circle, mp-anim-target)

  • Verified that MP.refresh() is definitely executed

  • Even with class/ID, animations don’t persist


What I’m asking:

  • Is there a reliable way to bind animations to dynamically injected elements, and retain them in the Timeline?

  • Or is there a specific scan/init pattern Motion.page expects for dynamic content to be “storable”?

Would really appreciate your insight — love what you’re building and this is the last piece missing in a perfect use case!

Thanks so much
— Jamal

function insertInfoIcon({ step, type = "checkbox", index = 0, tooltip = "" }) {
  const container = document.querySelector(`.forminator-pagination[data-step="${step}"]`);
  if (!container) return;

  const selector = type === "radio" ? "label.forminator-radio" : "label.forminator-checkbox";
  const labels = container.querySelectorAll(selector);

  if (labels.length > index) {
    const label = labels[index];
    if (!label.querySelector('.aoun-info-circle')) {
      const info = document.createElement("span");
      info.className = "aoun-info-circle";
      info.textContent = "i";
      info.setAttribute("mp-cursor-tooltip", tooltip);
      label.appendChild(info);
    }
  }
}

function generateIconsFromHTML() {
  const tooltipContainer = document.getElementById("tooltip-data");
  if (!tooltipContainer) return;

  const items = tooltipContainer.querySelectorAll("[data-tooltip]");
  items.forEach(item => {
    const key = item.getAttribute("data-tooltip");
    const text = item.textContent.trim();
    const match = key.match(/^step-(\d+)-(checkbox|radio)-(\d+)$/);
    if (!match) return;

    const [ , step, type, index ] = match;
    insertInfoIcon({ step: parseInt(step), type, index: parseInt(index), tooltip: text });
  });
}

document.addEventListener("DOMContentLoaded", () => {
  let attempts = 0;
  const maxAttempts = 30;

  const interval = setInterval(() => {
    const loaded = document.querySelector('.forminator-pagination[data-step]');
    const tooltipReady = document.getElementById('tooltip-data');

    if (loaded && tooltipReady) {
      clearInterval(interval);
      generateIconsFromHTML();

      // Re-scan Motion.page tooltips
      if (typeof MP !== 'undefined' && typeof MP.refresh === 'function') {
        MP.refresh();
      }
    }

    if (++attempts >= maxAttempts) {
      clearInterval(interval);
    }
  }, 300);
});