/******/ (() => { // webpackBootstrap
/******/ 	"use strict";
/******/ 	// The require scope
/******/ 	var __webpack_require__ = {};
/******/ 	
/************************************************************************/
/******/ 	/* webpack/runtime/define property getters */
/******/ 	(() => {
/******/ 		// define getter functions for harmony exports
/******/ 		__webpack_require__.d = (exports, definition) => {
/******/ 			for(var key in definition) {
/******/ 				if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/ 					Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ 				}
/******/ 			}
/******/ 		};
/******/ 	})();
/******/ 	
/******/ 	/* webpack/runtime/hasOwnProperty shorthand */
/******/ 	(() => {
/******/ 		__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ 	})();
/******/ 	
/************************************************************************/
var __webpack_exports__ = {};

// EXPORTS
__webpack_require__.d(__webpack_exports__, {
  TinyTextRangeEditor: () => (/* reexport */ libs_TinyTextRangeEditor)
});

;// ./src/v1/libs/TinyTextRangeEditor.mjs
/**
 * A full-featured text range editor for `<input>` and `<textarea>` elements,
 * including advanced utilities for BBCode or similar tag-based markup editing.
 */
class TinyTextRangeEditor {
  /** @type {HTMLInputElement | HTMLTextAreaElement} */
  #el;

  /** @type {string} */
  #openTag;

  /** @type {string} */
  #closeTag;

  /**
   * @param {HTMLInputElement | HTMLTextAreaElement} elem - The target editable input or textarea element.
   * @param {Object} [settings={}] - Optional tag symbol customization.
   * @param {string} [settings.openTag='['] - The character or symbol used to start a tag (e.g., `'['`).
   * @param {string} [settings.closeTag=']'] - The character or symbol used to end a tag (e.g., `']'`).
   */
  constructor(elem, { openTag = '[', closeTag = ']' } = {}) {
    if (!(elem instanceof HTMLInputElement || elem instanceof HTMLTextAreaElement))
      throw new TypeError('Element must be an input or textarea.');
    if (typeof openTag !== 'string') throw new TypeError('openTag must be a string.');
    if (typeof closeTag !== 'string') throw new TypeError('closeTag must be a string.');
    this.#el = elem;
    this.#openTag = openTag;
    this.#closeTag = closeTag;
  }

  /** @returns {string} The current open tag symbol. */
  getOpenTag() {
    return this.#openTag;
  }

  /** @returns {string} The current close tag symbol. */
  getCloseTag() {
    return this.#closeTag;
  }

  /** @param {string} tag - New open tag symbol to use (e.g., `'['`). */
  setOpenTag(tag) {
    if (typeof tag !== 'string') throw new TypeError('Open tag must be a string.');
    this.#openTag = tag;
  }

  /** @param {string} tag - New close tag symbol to use (e.g., `']'`). */
  setCloseTag(tag) {
    if (typeof tag !== 'string') throw new TypeError('Close tag must be a string.');
    this.#closeTag = tag;
  }

  /**
   * Ensures the element has focus.
   * @returns {TinyTextRangeEditor}
   */
  ensureFocus() {
    if (document.activeElement !== this.#el) this.#el.focus();
    return this;
  }

  /**
   * Focus the element.
   * @returns {TinyTextRangeEditor}
   */
  focus() {
    this.#el.focus();
    return this;
  }

  /** @returns {{ start: number, end: number }} The current selection range. */
  getSelectionRange() {
    return {
      start: this.#el.selectionStart ?? NaN,
      end: this.#el.selectionEnd ?? NaN,
    };
  }

  /**
   * Sets the current selection range.
   * @param {number} start - Start index.
   * @param {number} end - End index.
   * @param {boolean} [preserveScroll=true] - Whether to preserve scroll position.
   * @returns {TinyTextRangeEditor}
   */
  setSelectionRange(start, end, preserveScroll = true) {
    if (typeof start !== 'number' || typeof end !== 'number')
      throw new TypeError('start and end must be numbers.');
    if (typeof preserveScroll !== 'boolean')
      throw new TypeError('preserveScroll must be a boolean.');
    const scrollTop = this.#el.scrollTop;
    const scrollLeft = this.#el.scrollLeft;
    this.#el.setSelectionRange(start, end);
    if (preserveScroll) {
      this.#el.scrollTop = scrollTop;
      this.#el.scrollLeft = scrollLeft;
    }
    return this;
  }

  /** @returns {string} The full current text value. */
  getValue() {
    return this.#el.value;
  }

  /**
   * Sets the full value of the element.
   * @param {string} value - The new value to assign.
   * @returns {TinyTextRangeEditor}
   */
  setValue(value) {
    if (typeof value !== 'string') throw new TypeError('Value must be a string.');
    this.#el.value = value;
    return this;
  }

  /** @returns {string} The currently selected text. */
  getSelectedText() {
    const { start, end } = this.getSelectionRange();
    return this.#el.value.slice(start, end);
  }

  /**
   * Inserts text at the current selection, replacing any selected content.
   * @param {string} text - The text to insert.
   * @param {Object} [settings={}] - Optional auto-spacing behavior.
   * @param {'start' | 'end' | 'preserve'} [settings.newCursor='end'] - Controls caret position after insertion.
   * @param {boolean} [settings.autoSpacing=false]
   * @param {boolean} [settings.autoSpaceLeft=false]
   * @param {boolean} [settings.autoSpaceRight=false]
   * @returns {TinyTextRangeEditor}
   */
  insertText(
    text,
    {
      newCursor = 'end',
      autoSpacing = false,
      autoSpaceLeft = autoSpacing,
      autoSpaceRight = autoSpacing,
    } = {},
  ) {
    if (typeof text !== 'string') throw new TypeError('Text must be a string.');
    if (!['start', 'end', 'preserve'].includes(newCursor))
      throw new TypeError("newCursor must be one of 'start', 'end', or 'preserve'.");
    if (typeof autoSpacing !== 'boolean') throw new TypeError('autoSpacing must be a boolean.');
    if (typeof autoSpaceLeft !== 'boolean') throw new TypeError('autoSpaceLeft must be a boolean.');
    if (typeof autoSpaceRight !== 'boolean')
      throw new TypeError('autoSpaceRight must be a boolean.');

    const { start, end } = this.getSelectionRange();
    const value = this.#el.value;

    const leftChar = value[start - 1] || '';
    const rightChar = value[end] || '';

    const addLeft = autoSpaceLeft && leftChar && !/\s/.test(leftChar);
    const addRight = autoSpaceRight && rightChar && !/\s/.test(rightChar);

    const finalText = `${addLeft ? ' ' : ''}${text}${addRight ? ' ' : ''}`;

    const newValue = value.slice(0, start) + finalText + value.slice(end);
    this.setValue(newValue);

    let cursorPos = start;
    if (newCursor === 'end') cursorPos = start + finalText.length;
    else if (newCursor === 'preserve') cursorPos = start;

    this.setSelectionRange(cursorPos, cursorPos);
    return this;
  }

  /**
   * Deletes the currently selected text.
   * @returns {TinyTextRangeEditor}
   */
  deleteSelection() {
    this.insertText('');
    return this;
  }

  /**
   * Replaces the selection using a transformation function.
   * @param {(selected: string) => string} transformer - Function that modifies the selected text.
   * @returns {TinyTextRangeEditor}
   */
  transformSelection(transformer) {
    if (typeof transformer !== 'function') throw new TypeError('transformer must be a function.');
    const { start } = this.getSelectionRange();
    const selected = this.getSelectedText();
    const transformed = transformer(selected);
    this.insertText(transformed);
    this.setSelectionRange(start, start + transformed.length);
    return this;
  }

  /**
   * Surrounds current selection with prefix and suffix.
   * @param {string} prefix - Text to insert before.
   * @param {string} suffix - Text to insert after.
   * @returns {TinyTextRangeEditor}
   */
  surroundSelection(prefix, suffix) {
    if (typeof prefix !== 'string' || typeof suffix !== 'string')
      throw new TypeError('prefix and suffix must be strings.');
    const selected = this.getSelectedText();
    this.insertText(`${prefix}${selected}${suffix}`);
    return this;
  }

  /**
   * Moves the caret by a given offset.
   * @param {number} offset - Characters to move.
   * @returns {TinyTextRangeEditor}
   */
  moveCaret(offset) {
    if (typeof offset !== 'number') throw new TypeError('offset must be a number.');
    const { start } = this.getSelectionRange();
    const pos = Math.max(0, start + offset);
    this.setSelectionRange(pos, pos);
    return this;
  }

  /**
   * Selects all content in the field.
   * @returns {TinyTextRangeEditor}
   */
  selectAll() {
    this.setSelectionRange(0, this.#el.value.length);
    return this;
  }

  /**
   * Expands the current selection by character amounts.
   * @param {number} before - Characters to expand to the left.
   * @param {number} after - Characters to expand to the right.
   * @returns {TinyTextRangeEditor}
   */
  expandSelection(before, after) {
    if (typeof before !== 'number' || typeof after !== 'number')
      throw new TypeError('before and after must be numbers.');
    const { start, end } = this.getSelectionRange();
    const newStart = Math.max(0, start - before);
    const newEnd = Math.min(this.#el.value.length, end + after);
    this.setSelectionRange(newStart, newEnd);
    return this;
  }

  /**
   * Replaces all regex matches in the content.
   * @param {RegExp} regex - Regex to match.
   * @param {(match: string) => string} replacer - Replacement function.
   * @returns {TinyTextRangeEditor}
   */
  replaceAll(regex, replacer) {
    if (!(regex instanceof RegExp)) throw new TypeError('regex must be a RegExp.');
    if (typeof replacer !== 'function') throw new TypeError('replacer must be a function.');
    const newValue = this.#el.value.replace(regex, replacer);
    this.setValue(newValue);
    return this;
  }

  /**
   * Replaces all regex matches within the currently selected text.
   *
   * @param {RegExp} regex - Regular expression to match inside selection.
   * @param {(match: string) => string} replacer - Function to replace each match.
   * @returns {TinyTextRangeEditor}
   */
  replaceInSelection(regex, replacer) {
    if (!(regex instanceof RegExp)) throw new TypeError('regex must be a RegExp.');
    if (typeof replacer !== 'function') throw new TypeError('replacer must be a function.');

    const { start, end } = this.getSelectionRange();
    const original = this.#el.value;
    const selected = original.slice(start, end);

    const replaced = selected.replace(regex, replacer);

    const updated = original.slice(0, start) + replaced + original.slice(end);

    this.setValue(updated);
    this.setSelectionRange(start, start + replaced.length);
    return this;
  }

  /**
   * Toggles a code around the current selection.
   * If it's already wrapped, unwraps it.
   * @param {string} codeName - The code to toggle.
   * @returns {TinyTextRangeEditor}
   */
  toggleCode(codeName) {
    if (typeof codeName !== 'string') throw new TypeError('codeName must be a string.');
    const selected = this.getSelectedText();
    if (selected.startsWith(codeName) && selected.endsWith(codeName)) {
      const unwrapped = selected.slice(codeName.length, selected.length - codeName.length);
      this.insertText(unwrapped);
    } else {
      this.insertText(`${codeName}${selected}${codeName}`);
    }
    return this;
  }

  /**
   * Converts a list of attributes into a string suitable for tag insertion.
   *
   * This method supports both standard key-value attribute objects (e.g., `{ key: "value" }`)
   * and boolean-style attribute arrays (e.g., `[ "disabled", "autofocus" ]`).
   *
   * - Attributes passed as an array will render as boolean attributes (e.g., `disabled autofocus`)
   * - Attributes passed as an object will render as `key="value"` pairs (or just `key` if the value is an empty string)
   *
   * @param {Record<string, string> | string[]} attributes - The attributes to serialize into a tag string.
   *   - If an array: treated as a list of boolean-style attributes.
   *   - If an object: treated as key-value pairs.
   *
   * @throws {TypeError} If the array contains non-strings, or the object contains non-string values.
   * @returns {string} A string of serialized attributes for use inside a tag.
   *
   * @example
   * // Using object attributes
   * _insertAttr({ size: "12", color: "red" });
   * // Returns: 'size="12" color="red"'
   *
   * @example
   * // Using boolean attributes
   * _insertAttr(["disabled", "autofocus"]);
   * // Returns: 'disabled autofocus'
   *
   * @example
   * // Using mixed/empty object values
   * _insertAttr({ checked: "", class: "btn" });
   * // Returns: 'checked class="btn"'
   */
  _insertAttr(attributes) {
    // Reuse attribute logic
    let attrStr = '';

    if (Array.isArray(attributes)) {
      // string[]
      if (!attributes.every((attr) => typeof attr === 'string'))
        throw new TypeError('All entries in attributes array must be strings.');
      attrStr = attributes.map((attr) => `${attr}`).join(' ');
    } else if (typeof attributes === 'object' && attributes !== null) {
      // Record<string, string>
      attrStr = Object.entries(attributes)
        .map(([key, val]) => {
          if (typeof val !== 'string')
            throw new TypeError('All entries in attributes object must be strings.');
          return `${key}${val.length > 0 ? `="${val}"` : ''}`;
        })
        .join(' ');
    } else {
      throw new TypeError('attributes must be an object or an array of strings.');
    }

    return attrStr;
  }

  /**
   * Wraps the current selection with a tag, optionally including attributes.
   *
   * @param {string} tagName - The tag name (e.g., `b`, `color`, etc.).
   * @param {Record<string,string> | string[]} [attributes={}] - Optional attributes for the opening tag.
   *   - If an object: key-value pairs (e.g., `{ color: "red" }` → `color="red"`).
   *   - If an array: boolean attributes (e.g., `["disabled", "readonly"]`).
   *
   * @returns {TinyTextRangeEditor}
   */
  wrapWithTag(tagName, attributes = {}) {
    if (typeof tagName !== 'string') throw new TypeError('tagName must be a string.');
    const attrStr = this._insertAttr(attributes);
    const openTag = attrStr
      ? `${this.#openTag}${tagName} ${attrStr}${this.#closeTag}`
      : `${this.#openTag}${tagName}${this.#closeTag}`;
    const closeTag = `${this.#openTag}/${tagName}${this.#closeTag}`;
    this.surroundSelection(openTag, closeTag);
    return this;
  }

  /**
   * Inserts a tag with optional inner content.
   * @param {string} tagName - The tag to insert.
   * @param {string} [content=''] - Optional content between tags.
   * @param {Record<string,string> | string[]} [attributes={}] - Optional attributes or list of empty attributes.
   * @returns {TinyTextRangeEditor}
   */
  insertTag(tagName, content = '', attributes = {}) {
    if (typeof tagName !== 'string') throw new TypeError('tagName must be a string.');
    if (typeof content !== 'string') throw new TypeError('content must be a string.');

    const attrStr = this._insertAttr(attributes);
    const open = attrStr
      ? `${this.#openTag}${tagName} ${attrStr}${this.#closeTag}`
      : `${this.#openTag}${tagName}${this.#closeTag}`;
    const close = `${this.#openTag}/${tagName}${this.#closeTag}`;

    this.insertText(`${open}${content}${close}`);
    return this;
  }

  /**
   * Inserts a self-closing tag.
   * @param {string} tagName - The tag name.
   * @param {Record<string,string> | string[]} [attributes={}] - Optional attributes or list of empty attributes.
   * @returns {TinyTextRangeEditor}
   */
  insertSelfClosingTag(tagName, attributes = {}) {
    if (typeof tagName !== 'string') throw new TypeError('tagName must be a string.');

    const attrStr = this._insertAttr(attributes);
    const tag = attrStr
      ? `${this.#openTag}${tagName} ${attrStr}${this.#closeTag}`
      : `${this.#openTag}${tagName}${this.#closeTag}`;

    this.insertText(tag);
    return this;
  }

  /**
   * Toggles a tag around the current selection.
   * Supports tags with attributes. If already wrapped, it unwraps.
   * @param {string} tagName - The tag to toggle.
   * @param {Record<string,string> | string[]} [attributes={}] - Optional attributes to apply when wrapping.
   * @returns {TinyTextRangeEditor}
   */
  toggleTag(tagName, attributes = {}) {
    if (typeof tagName !== 'string') throw new TypeError('tagName must be a string.');
    const selected = this.getSelectedText();

    // Regex: opening tag with optional attributes, and closing tag
    const openRegex = new RegExp(`^\\[${tagName}(\\s+[^\\]]*)?\\]`);
    const closeRegex = new RegExp(`\\[/${tagName}\\]$`);

    const hasOpen = openRegex.test(selected);
    const hasClose = closeRegex.test(selected);

    if (hasOpen && hasClose) {
      const unwrapped = selected
        .replace(openRegex, '') // remove opening tag
        .replace(closeRegex, ''); // remove closing tag
      this.insertText(unwrapped);
    } else {
      const attrStr = this._insertAttr(attributes);
      const open = attrStr
        ? `${this.#openTag}${tagName} ${attrStr}${this.#closeTag}`
        : `${this.#openTag}${tagName}${this.#closeTag}`;
      const close = `${this.#openTag}/${tagName}${this.#closeTag}`;
      this.insertText(`${open}${selected}${close}`);
    }

    return this;
  }
}

/* harmony default export */ const libs_TinyTextRangeEditor = (TinyTextRangeEditor);

;// ./src/v1/build/TinyTextRangeEditor.mjs




window.TinyTextRangeEditor = __webpack_exports__.TinyTextRangeEditor;
/******/ })()
;