Source: TextBinder.js

/**
 * @author Greenwald
 * @license PDL
 * @module TextBinder
 */
import Binder from './Binder.js';

export default 
/**
 * @class TextBinder
 * @memberof module:TextBinder
 * @description Enables the use of `{data}` binders in text content.
 * @example
 * ```html
 * <html>
 *  <body>
 *   <h1>{title}</h1>
 *   <p>{content}</p>
 *  </body>
 * </html>
 * ```
 * 
 * ```javascript
 * var model = { title: 'Hello', content: 'World' };
 * var binder = new Binder(new TextBinder());
 * binder.bind(document.body, model);
 * ```
 */
class TextBinder extends Binder.Extension {
  static #parse = Symbol('parse');
  handleElement(binder, element, route) {
    if (!(element instanceof Text)) { return false; }
    if (element.parentElement instanceof HTMLStyleElement) { return false; }
    const parse = element[TextBinder.#parse] ??= TextBinder.parse(element.textContent);
    if (!parse.length) { return true; }
    const format = parse
      .map(p => `${p.text}${p.selector ? route.select(p.selector.split('.')).result?.toString() ?? '' : ''}`)
      .join('');
    if (element.textContent !== format) { element.textContent = format; }
    return true;
  }
  /**
   * @function parse
   * @memberof module:TextBinder.TextBinder
   * @description Parses a string into text/selector pairs.
   * @param {string} text The text to parse
   * @returns {Array} An array of parsed segments: {text, selector}
   */
  static parse(text) {
    var matches = [...(text??'').matchAll(/\{\{|\}\}|\{([^\}]+)\}/g)];
    if (matches.length === 0) { return []; }
    return matches.map((m,i) => ({
      text: text.substring(i > 0 ? matches[i-1].index + matches[i-1][0].length : 0, m.index)
          + (m[0] === '{{' ? '{' : m[0] === '}}' ? '}' : ''),
      selector: m[1] ? m[1].trim() : null
    })).concat([{ text: text.substring(matches.at(-1).index + matches.at(-1)[0].length) }]);
  }
}