tree.js

38.54 KB
26/05/2025 08:35
JS
tree.js
/**
 * TreeManager - คลาสจัดการโครงสร้างต้นไม้
 */
class TreeManager {
  /**
   * สร้าง TreeManager instance ใหม่
   * @param {Object} config - การตั้งค่าต้นไม้
   * @param {string} config.containerId - ID ของ element ที่จะแสดงต้นไม้
   * @param {boolean} config.limitedDepth - บ่งชี้ว่าจำกัดความลึกของระดับชั้นหรือไม่
   * @param {number} config.maxDepth - จำนวนระดับชั้นสูงสุด (เมื่อ limitedDepth เป็น true)
   * @param {Object} config.levelNames - ชื่อของแต่ละระดับชั้น {1: 'Level 1', 2: 'Level 2', ...}
   * @param {string} config.outputId - ID ของ element ที่จะแสดง JSON output
   */
  constructor(config) {
    this.containerId = config.containerId || 'tree';
    this.container = document.getElementById(this.containerId);
    this.limitedDepth = config.limitedDepth !== undefined ? config.limitedDepth : true;
    this.maxDepth = config.maxDepth || 3;
    this.levelNames = config.levelNames || {
      1: 'Stage',
      2: 'Area',
      3: 'Zone'
    };
    this.outputId = config.outputId || 'json-output';
    this.outputElement = document.getElementById(this.outputId);
    this.data = [];
    this.nextId = 1;
    this.draggedItem = null;
    this.dropTarget = null;
    this.dropPosition = null; // 'before', 'after', 'inside'
    this.dragGhost = null;
    this.dropBeforeIndicator = null;
    this.dropAfterIndicator = null;
    this.dropChildIndicator = null;
    this._createDropIndicators();
    this._setupDragAndDrop();
  }

  /**
   * สร้างตัวชี้บอกตำแหน่งการวาง
   * @private
   */
  _createDropIndicators() {
    this.dropBeforeIndicator = document.createElement('div');
    this.dropBeforeIndicator.className = 'drop-indicator drop-before';
    this.dropAfterIndicator = document.createElement('div');
    this.dropAfterIndicator.className = 'drop-indicator drop-after';
    this.dropChildIndicator = document.createElement('div');
    this.dropChildIndicator.className = 'drop-indicator-child';
    this.dropChildIndicator.textContent = 'Place as child';
    this.indentIndicator = document.createElement('div');
    this.indentIndicator.className = 'drop-indicator-level indent';
    this.indentIndicator.innerHTML = '<span>Increase level</span>';
    this.outdentIndicator = document.createElement('div');
    this.outdentIndicator.className = 'drop-indicator-level outdent';
    this.outdentIndicator.innerHTML = '<span>Decrease level</span>';
  }

  /**
   * กำหนด event listeners สำหรับการลากวาง
   * @private
   */
  _setupDragAndDrop() {
    document.addEventListener('dragstart', (e) => {
      if (e.target.classList.contains('tree-content')) {
        const itemId = parseInt(e.target.closest('.tree-item').dataset.id);
        this.draggedItem = this._findNodeById(itemId, this.data);
        if (!this.draggedItem) return;
        e.dataTransfer.setData('text/plain', itemId);
        const ghost = this._createDragGhost(e.target);
        document.body.appendChild(ghost);
        const offsetX = e.clientX - e.target.getBoundingClientRect().left;
        const offsetY = e.clientY - e.target.getBoundingClientRect().top;
        e.dataTransfer.setDragImage(ghost, offsetX, offsetY);
        this.dragGhost = ghost;
        e.target.classList.add('dragging');
        this._createDropZones();
        this._highlightValidDropTargets();
      }
    });

    document.addEventListener('dragover', (e) => {
      if (!this.draggedItem) return;
      e.preventDefault();
      const targetItem = e.target.closest('.tree-item');
      if (!targetItem) {
        this._hideDropHighlights();
        return;
      }
      const targetId = parseInt(targetItem.dataset.id);
      const targetNode = this._findNodeById(targetId, this.data);
      if (!targetNode) {
        this._hideDropHighlights();
        return;
      }
      if (this.draggedItem.id === targetNode.id || this._isDescendantOf(targetNode.id, this.draggedItem)) {
        this._hideDropHighlights();
        return;
      }
      const targetRect = targetItem.getBoundingClientRect();
      const mouseY = e.clientY;
      const topZone = targetRect.top + (targetRect.height * 0.3);
      const bottomZone = targetRect.bottom - (targetRect.height * 0.3);
      let position;
      if (mouseY < topZone) {
        position = 'before';
      } else if (mouseY > bottomZone) {
        position = 'after';
      } else {
        position = 'inside';
      }
      let canDrop = false;
      if (position === 'inside') {
        canDrop = this._canDropAsChild(this.draggedItem, targetNode);
      } else {
        canDrop = this._canDropInSameLevel(this.draggedItem, targetNode);
      }
      if (!canDrop) {
        this._hideDropHighlights();
        return;
      }
      this.dropTarget = targetNode;
      this.dropPosition = position;
      this._showDropHighlight(targetItem, position);
    });

    document.addEventListener('dragleave', (e) => {
      if (!e.target.closest('.tree-item') && !e.relatedTarget?.closest('.tree-item')) {
        this._hideDropHighlights();
      }
    });

    document.addEventListener('drop', (e) => {
      e.preventDefault();
      if (!this.draggedItem || !this.dropTarget || !this.dropPosition) {
        this._cleanupDrag();
        return;
      }
      this.moveNode(this.draggedItem.id, this.dropTarget.id, this.dropPosition);
      this._cleanupDrag();
    });

    document.addEventListener('dragend', () => {
      this._cleanupDrag();
    });
  }

  /**
   * สร้าง dropzones เมื่อเริ่มต้นลาก
   * @private
   */
  _createDropZones() {
    this.dropZonesContainer = document.createElement('div');
    this.dropZonesContainer.className = 'drop-zones-container';
    this.dropZonesContainer.style.position = 'absolute';
    this.dropZonesContainer.style.pointerEvents = 'none';
    this.dropZonesContainer.style.zIndex = '1000';
    document.body.appendChild(this.dropZonesContainer);
    this.beforeDropZone = document.createElement('div');
    this.beforeDropZone.className = 'drop-zone drop-before';
    this.beforeDropZone.style.height = '4px';
    this.beforeDropZone.style.background = 'transparent';
    this.beforeDropZone.style.position = 'absolute';
    this.beforeDropZone.style.display = 'none';
    this.dropZonesContainer.appendChild(this.beforeDropZone);
    this.afterDropZone = document.createElement('div');
    this.afterDropZone.className = 'drop-zone drop-after';
    this.afterDropZone.style.height = '4px';
    this.afterDropZone.style.background = 'transparent';
    this.afterDropZone.style.position = 'absolute';
    this.afterDropZone.style.display = 'none';
    this.dropZonesContainer.appendChild(this.afterDropZone);
    this.insideDropZone = document.createElement('div');
    this.insideDropZone.className = 'drop-zone drop-inside';
    this.insideDropZone.style.background = 'transparent';
    this.insideDropZone.style.position = 'absolute';
    this.insideDropZone.style.display = 'none';
    this.dropZonesContainer.appendChild(this.insideDropZone);
  }

  /**
   * เพิ่ม highlight ให้กับโหนดทั้งหมดที่สามารถรับการวางได้
   * @private
   */
  _highlightValidDropTargets() {
    const allItems = document.querySelectorAll('.tree-item');
    allItems.forEach(item => {
      item.classList.remove('valid-drop-target');
    });
    allItems.forEach(item => {
      const itemId = parseInt(item.dataset.id);
      if (itemId !== this.draggedItem.id) {
        const node = this._findNodeById(itemId, this.data);
        if (node && !this._isDescendantOf(node.id, this.draggedItem)) {
          item.classList.add('valid-drop-target');
        }
      }
    });
  }

  /**
   * แสดงตำแหน่งวางโดยไม่มีการสร้าง/ลบ DOM elements
   * @param {HTMLElement} targetElement - Element เป้าหมายที่จะวาง
   * @param {string} position - ตำแหน่งที่จะวาง ('before', 'after', 'inside')
   * @private
   */
  _showDropHighlight(targetElement, position) {
    this._hideDropHighlights();
    const rect = targetElement.getBoundingClientRect();
    if (position === 'before') {
      this.beforeDropZone.style.display = 'block';
      this.beforeDropZone.style.top = (rect.top - 2) + 'px';
      this.beforeDropZone.style.left = rect.left + 'px';
      this.beforeDropZone.style.width = rect.width + 'px';
      this.beforeDropZone.style.background = 'var(--primary-color)';
      this.beforeDropZone.style.animation = 'pulse 1.5s infinite';
    } else if (position === 'after') {
      this.afterDropZone.style.display = 'block';
      this.afterDropZone.style.top = (rect.bottom - 2) + 'px';
      this.afterDropZone.style.left = rect.left + 'px';
      this.afterDropZone.style.width = rect.width + 'px';
      this.afterDropZone.style.background = 'var(--primary-color)';
      this.afterDropZone.style.animation = 'pulse 1.5s infinite';
    } else if (position === 'inside') {
      this.insideDropZone.style.display = 'block';
      this.insideDropZone.style.top = rect.top + 'px';
      this.insideDropZone.style.left = rect.left + 'px';
      this.insideDropZone.style.width = rect.width + 'px';
      this.insideDropZone.style.height = rect.height + 'px';
      this.insideDropZone.style.border = '2px dashed var(--primary-color)';
      this.insideDropZone.style.backgroundColor = 'rgba(52, 152, 219, 0.1)';
      this.insideDropZone.style.borderRadius = '4px';
      this.insideDropZone.style.animation = 'glow 1.5s infinite alternate';
    }
    targetElement.querySelector('.tree-content').classList.add('over-' + position);
  }

  /**
   * ซ่อน highlight ทั้งหมด
   * @private
   */
  _hideDropHighlights() {
    if (this.beforeDropZone) this.beforeDropZone.style.display = 'none';
    if (this.afterDropZone) this.afterDropZone.style.display = 'none';
    if (this.insideDropZone) this.insideDropZone.style.display = 'none';
    const highlightedElements = document.querySelectorAll('.over-before, .over-after, .over-inside');
    highlightedElements.forEach(el => {
      el.classList.remove('over-before', 'over-after', 'over-inside');
    });
  }

  /**
   * ล้างข้อมูลการลากและซ่อนตัวชี้ทั้งหมด
   * @private
   */
  _cleanupDrag() {
    this._hideDropHighlights();
    if (this.dropZonesContainer && this.dropZonesContainer.parentNode) {
      this.dropZonesContainer.parentNode.removeChild(this.dropZonesContainer);
      this.dropZonesContainer = null;
      this.beforeDropZone = null;
      this.afterDropZone = null;
      this.insideDropZone = null;
    }
    const validTargets = document.querySelectorAll('.valid-drop-target');
    validTargets.forEach(item => {
      item.classList.remove('valid-drop-target');
    });
    if (this.dragGhost && this.dragGhost.parentNode) {
      this.dragGhost.parentNode.removeChild(this.dragGhost);
    }
    const draggingItems = document.querySelectorAll('.dragging');
    draggingItems.forEach(item => item.classList.remove('dragging'));
    this.draggedItem = null;
    this.dropTarget = null;
    this.dropPosition = null;
  }

  /**
   * สร้างธีมของ element ที่ถูกลาก
   * @param {HTMLElement} original - Element ต้นฉบับ
   * @returns {HTMLElement} - Element สำหรับแสดงขณะลาก
   * @private
   */
  _createDragGhost(original) {
    const ghost = original.cloneNode(true);
    ghost.classList.add('drag-ghost');
    ghost.style.position = 'absolute';
    ghost.style.top = '-1000px';
    ghost.style.opacity = '0.7';
    return ghost;
  }

  /**
   * ซ่อนตัวชี้ตำแหน่งการวางทั้งหมด
   * @private
   */
  _hideDropIndicator() {
    [
      this.dropBeforeIndicator,
      this.dropAfterIndicator,
      this.dropChildIndicator,
      this.indentIndicator,
      this.outdentIndicator
    ].forEach(indicator => {
      if (indicator && indicator.parentNode) {
        indicator.parentNode.removeChild(indicator);
      }
      if (indicator) {
        indicator.style.display = 'none';
      }
    });
  }

  /**
   * ตรวจสอบว่าสามารถวางเป็นลูกของโหนดเป้าหมายได้หรือไม่
   * @param {Object} dragNode - โหนดที่ถูกลาก
   * @param {Object} targetNode - โหนดเป้าหมาย
   * @returns {boolean} - สามารถวางได้หรือไม่
   * @private
   */
  _canDropAsChild(dragNode, targetNode) {
    if (this.limitedDepth) {
      const newLevel = targetNode.level + 1;
      if (newLevel > this.maxDepth) {
        return false;
      }
    }
    return true;
  }

  /**
   * ตรวจสอบว่าสามารถวางในระดับเดียวกับโหนดเป้าหมายได้หรือไม่
   * @param {Object} dragNode - โหนดที่ถูกลาก
   * @param {Object} targetNode - โหนดเป้าหมาย
   * @returns {boolean} - สามารถวางได้หรือไม่
   * @private
   */
  _canDropInSameLevel(dragNode, targetNode) {
    if (targetNode.level === 1 && !targetNode.parentId) {
      return true;
    }
    const targetParent = this._findParentNode(targetNode.id);
    if (!targetParent) {
      return true;
    }
    return true;
  }

  /**
   * ตรวจสอบว่าโหนด A เป็นลูกหลานของโหนด B หรือไม่
   * @param {number} nodeAId - ID ของโหนด A
   * @param {Object} nodeB - โหนด B
   * @returns {boolean} - เป็นลูกหลานหรือไม่
   * @private
   */
  _isDescendantOf(nodeAId, nodeB) {
    if (!nodeB.children || nodeB.children.length === 0) {
      return false;
    }
    for (const child of nodeB.children) {
      if (child.id === nodeAId || this._isDescendantOf(nodeAId, child)) {
        return true;
      }
    }
    return false;
  }

  /**
   * หาโหนดตาม ID
   * @param {number} id - ID ของโหนดที่ต้องการหา
   * @param {Array} nodes - อาร์เรย์ของโหนดที่จะค้นหา (default: this.data)
   * @returns {Object|null} - โหนดที่พบ หรือ null ถ้าไม่พบ
   * @private
   */
  _findNodeById(id, nodes = this.data) {
    for (const node of nodes) {
      if (node.id === id) {
        return node;
      }
      if (node.children && node.children.length > 0) {
        const found = this._findNodeById(id, node.children);
        if (found) {
          return found;
        }
      }
    }
    return null;
  }

  /**
       * หาโหนดพ่อของโหนดที่กำหนด
       * @param {number} nodeId - ID ของโหนดที่ต้องการหาพ่อ
       * @param {Array} nodes - อาร์เรย์ของโหนดที่จะค้นหา (default: this.data)
       * @returns {Object|null} - โหนดพ่อที่พบ หรือ null ถ้าไม่พบ
       * @private
       */
  _findParentNode(nodeId, nodes = this.data) {
    for (const node of nodes) {
      if (node.children && node.children.length > 0) {
        for (const child of node.children) {
          if (child.id === nodeId) {
            return node;
          }
        }
        const found = this._findParentNode(nodeId, node.children);
        if (found) {
          return found;
        }
      }
    }
    return null;
  }

  /**
  * คำนวณความลึกสูงสุดของโหนด
  * @param {Object} node - โหนดที่ต้องการคำนวณความลึก
  * @returns {number} - ความลึกสูงสุด
  * @private
  */
  _getMaxDepth(node) {
    if (!node.children || node.children.length === 0) {
      return node.level;
    }
    let maxChildDepth = 0;
    for (const child of node.children) {
      const childDepth = this._getMaxDepth(child);
      if (childDepth > maxChildDepth) {
        maxChildDepth = childDepth;
      }
    }
    return maxChildDepth;
  }

  /**
  * อัพเดตระดับของโหนดและลูกหลานทั้งหมด
  * @param {Object} node - โหนดที่ต้องการอัพเดตระดับ
  * @param {number} newLevel - ระดับใหม่
  * @private
  */
  _updateNodeLevel(node, newLevel) {
    node.level = newLevel;
    if (node.children && node.children.length > 0) {
      for (const child of node.children) {
        this._updateNodeLevel(child, newLevel + 1);
      }
    }
  }

  /**
  * เพิ่มโหนดระดับบนสุด
  * @param {string} name - ชื่อของโหนด
  * @returns {Object} - โหนดที่ถูกสร้าง
  */
  addRootNode(name = 'New item') {
    const newNode = {
      id: this.nextId++,
      name: name,
      level: 1,
      children: []
    };
    this.data.push(newNode);
    this._renderTree();
    this._updateResult();
    return newNode;
  }

  /**
  * เพิ่มโหนดลูก
  * @param {number} parentId - ID ของโหนดพ่อ
  * @param {string} name - ชื่อของโหนด
  * @returns {Object|null} - โหนดที่ถูกสร้าง หรือ null ถ้าไม่สามารถสร้างได้
  */
  addChildNode(parentId, name = 'Child item') {
    const parent = this._findNodeById(parentId);
    if (!parent) {
      return null;
    }
    if (this.limitedDepth && parent.level >= this.maxDepth) {
      console.warn('Cannot add: Exceeds maximum depth limit');
      return null;
    }
    const newNode = {
      id: this.nextId++,
      name: name,
      level: parent.level + 1,
      parentId: parent.id,
      children: []
    };
    if (!parent.children) {
      parent.children = [];
    }
    parent.children.push(newNode);
    this._renderTree();
    this._updateResult();
    return newNode;
  }

  /**
  * ลบโหนด
  * @param {number} nodeId - ID ของโหนดที่ต้องการลบ
  * @returns {boolean} - สำเร็จหรือไม่
  */
  deleteNode(nodeId) {
    const node = this._findNodeById(nodeId);
    if (!node) {
      return false;
    }
    if (!node.parentId) {
      const index = this.data.findIndex(item => item.id === nodeId);
      if (index !== -1) {
        this.data.splice(index, 1);
      }
    } else {
      const parent = this._findParentNode(nodeId);
      if (parent && parent.children) {
        const index = parent.children.findIndex(item => item.id === nodeId);
        if (index !== -1) {
          parent.children.splice(index, 1);
        }
      }
    }
    this._renderTree();
    this._updateResult();
    return true;
  }

  /**
  * อัพเดตชื่อของโหนด
  * @param {number} nodeId - ID ของโหนดที่ต้องการอัพเดต
  * @param {string} newName - ชื่อใหม่
  * @returns {boolean} - สำเร็จหรือไม่
  * @private
  */
  _updateNodeName(nodeId, newName) {
    const node = this._findNodeById(nodeId);
    if (!node) {
      return false;
    }
    node.name = newName;
    this._updateResult();
    return true;
  }

  /**
   * ย้ายโหนด
   * @param {number} nodeId - ID ของโหนดที่ต้องการย้าย
   * @param {number} targetId - ID ของโหนดเป้าหมาย
   * @param {string} position - ตำแหน่งที่จะวาง ('before', 'after', 'inside', 'indent', 'outdent')
   * @returns {boolean} - สำเร็จหรือไม่
   */
  moveNode(nodeId, targetId, position) {
    const node = this._findNodeById(nodeId);
    const target = this._findNodeById(targetId);
    if (!node || !target) {
      return false;
    }
    if (!node.parentId) {
      const index = this.data.findIndex(item => item.id === nodeId);
      if (index !== -1) {
        this.data.splice(index, 1);
      }
    } else {
      const parent = this._findParentNode(nodeId);
      if (parent && parent.children) {
        const index = parent.children.findIndex(item => item.id === nodeId);
        if (index !== -1) {
          parent.children.splice(index, 1);
        }
      }
    }
    if (position === 'indent') {
      const previous = this._findPreviousSibling(targetId);
      if (previous) {
        node.parentId = previous.id;
        node.level = previous.level + 1;
        if (!previous.children) {
          previous.children = [];
        }
        previous.children.push(node);
      } else {
        node.parentId = target.id;
        node.level = target.level + 1;
        if (!target.children) {
          target.children = [];
        }
        target.children.push(node);
      }
    } else if (position === 'outdent') {
      const targetParent = this._findParentNode(targetId);
      if (targetParent) {
        node.parentId = targetParent.parentId;
        node.level = targetParent.level;
        if (!targetParent.parentId) {
          const index = this.data.findIndex(item => item.id === targetParent.id);
          this.data.splice(index + 1, 0, node);
        } else {
          const grandParent = this._findParentNode(targetParent.id);
          const index = grandParent.children.findIndex(item => item.id === targetParent.id);
          grandParent.children.splice(index + 1, 0, node);
        }
      } else {
        node.parentId = null;
        node.level = 1;
        const index = this.data.findIndex(item => item.id === targetId);
        this.data.splice(index + 1, 0, node);
      }
    } else if (position === 'inside') {
      node.parentId = target.id;
      node.level = target.level + 1;
      if (!target.children) {
        target.children = [];
      }
      target.children.push(node);
    } else {
      if (!target.parentId) {
        node.parentId = null;
        node.level = 1;
        const index = this.data.findIndex(item => item.id === targetId);
        if (position === 'before') {
          this.data.splice(index, 0, node);
        } else {
          this.data.splice(index + 1, 0, node);
        }
      } else {
        const parent = this._findParentNode(targetId);
        if (parent && parent.children) {
          node.parentId = parent.id;
          node.level = target.level;
          const index = parent.children.findIndex(item => item.id === targetId);
          if (position === 'before') {
            parent.children.splice(index, 0, node);
          } else {
            parent.children.splice(index + 1, 0, node);
          }
        }
      }
    }
    if (node.children && node.children.length > 0) {
      for (const child of node.children) {
        this._updateNodeLevel(child, node.level + 1);
      }
    }
    this._renderTree();
    this._updateResult();
    return true;
  }

  /**
   * หาโหนดพี่น้องก่อนหน้าของโหนดที่กำหนด
   * @param {number} nodeId - ID ของโหนดที่ต้องการหาพี่น้องก่อนหน้า
   * @returns {Object|null} - โหนดพี่น้องก่อนหน้าที่พบ หรือ null ถ้าไม่พบ
   * @private
   */
  _findPreviousSibling(nodeId) {
    const node = this._findNodeById(nodeId);
    if (!node) return null;
    if (!node.parentId) {
      const index = this.data.findIndex(item => item.id === nodeId);
      if (index > 0) {
        return this.data[index - 1];
      }
    } else {
      const parent = this._findParentNode(nodeId);
      if (parent && parent.children) {
        const index = parent.children.findIndex(item => item.id === nodeId);
        if (index > 0) {
          return parent.children[index - 1];
        }
      }
    }
    return null;
  }

  /**
  * สลับสถานะการยุบ/ขยายของโหนด
  * @param {number} nodeId - ID ของโหนดที่ต้องการสลับสถานะ
  * @private
  */
  _toggleCollapse(nodeId) {
    const itemElement = document.querySelector(`.tree-item[data-id="${nodeId}"]`);
    if (itemElement) {
      itemElement.classList.toggle('collapsed');
    }
  }

  /**
  * ยุบโหนดทั้งหมด
  */
  collapseAll() {
    const items = document.querySelectorAll('.tree-item');
    items.forEach(item => {
      if (item.querySelector('.tree-children')) {
        item.classList.add('collapsed');
      }
    });
  }

  /**
  * ขยายโหนดทั้งหมด
  */
  expandAll() {
    const items = document.querySelectorAll('.tree-item');
    items.forEach(item => {
      item.classList.remove('collapsed');
    });
  }

  /**
  * สร้าง DOM element สำหรับรายการ
  * @param {Object} node - ข้อมูลโหนด
  * @returns {HTMLElement} - Element ที่สร้าง
  * @private
  */
  _createTreeItemElement(node) {
    const item = document.createElement('div');
    item.className = 'tree-item';
    item.dataset.id = node.id;
    const content = document.createElement('div');
    content.className = 'tree-content';
    content.draggable = true;
    content.classList.add(`level-${node.level}`);
    const hasChildren = node.children && node.children.length > 0;
    const collapseBtn = document.createElement('span');
    collapseBtn.className = 'collapse-btn';
    collapseBtn.innerHTML = hasChildren ? '▼' : '&nbsp;';
    collapseBtn.addEventListener('click', () => {
      if (hasChildren) {
        this._toggleCollapse(node.id);
        collapseBtn.innerHTML = item.classList.contains('collapsed') ? '►' : '▼';
      }
    });
    content.appendChild(collapseBtn);
    const label = document.createElement('div');
    label.className = 'tree-label';
    const levelTag = document.createElement('span');
    levelTag.className = 'tree-level-tag';
    const levelName = this.levelNames[node.level] || `Level ${node.level}`;
    levelTag.textContent = levelName;
    label.appendChild(levelTag);
    const nameInput = document.createElement('input');
    nameInput.type = 'text';
    nameInput.className = 'tree-name';
    nameInput.value = node.name;
    nameInput.addEventListener('change', () => {
      this._updateNodeName(node.id, nameInput.value);
    });
    nameInput.addEventListener('keydown', (e) => {
      if (e.key === 'Enter') {
        nameInput.blur();
      }
    });
    label.appendChild(nameInput);
    content.appendChild(label);
    const actionsMenu = document.createElement('div');
    actionsMenu.className = 'tree-actions-menu';
    const moveUpBtn = document.createElement('button');
    moveUpBtn.className = 'tree-action-btn';
    moveUpBtn.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12 19V5M12 5L5 12M12 5L19 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
    moveUpBtn.title = 'Move up';
    moveUpBtn.addEventListener('click', () => {
      this.moveNodeUp(node.id);
    });
    actionsMenu.appendChild(moveUpBtn);
    const moveDownBtn = document.createElement('button');
    moveDownBtn.className = 'tree-action-btn';
    moveDownBtn.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12 5V19M12 19L5 12M12 19L19 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
    moveDownBtn.title = 'Move down';
    moveDownBtn.addEventListener('click', () => {
      this.moveNodeDown(node.id);
    });
    actionsMenu.appendChild(moveDownBtn);
    const moveLeftBtn = document.createElement('button');
    moveLeftBtn.className = 'tree-action-btn';
    moveLeftBtn.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M19 12H5M5 12L12 5M5 12L12 19" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
    moveLeftBtn.title = 'Move out (up one level)';
    moveLeftBtn.addEventListener('click', () => {
      this.moveNodeLeft(node.id);
    });
    actionsMenu.appendChild(moveLeftBtn);
    const moveRightBtn = document.createElement('button');
    moveRightBtn.className = 'tree-action-btn';
    moveRightBtn.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M5 12H19M19 12L12 5M19 12L12 19" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
    moveRightBtn.title = 'Move in (down one level)';
    moveRightBtn.addEventListener('click', () => {
      this.moveNodeRight(node.id);
    });
    actionsMenu.appendChild(moveRightBtn);
    const addBtn = document.createElement('button');
    addBtn.className = 'tree-action-btn';
    addBtn.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12 4V20M4 12H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
    addBtn.title = 'Add child item';
    addBtn.addEventListener('click', () => {
      if (this.limitedDepth && node.level >= this.maxDepth) {
        alert(`Cannot add child item: Exceeds maximum depth limit (${this.maxDepth} levels)`);
        return;
      }
      this.addChildNode(node.id);
    });
    actionsMenu.appendChild(addBtn);
    const deleteBtn = document.createElement('button');
    deleteBtn.className = 'tree-action-btn';
    deleteBtn.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M19 7L18.1327 19.1425C18.0579 20.1891 17.187 21 16.1378 21H7.86224C6.81296 21 5.94208 20.1891 5.86732 19.1425L5 7M10 11V17M14 11V17M15 7V4C15 3.44772 14.5523 3 14 3H10C9.44772 3 9 3.44772 9 4V7M4 7H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
    deleteBtn.title = 'Delete this item';
    deleteBtn.addEventListener('click', () => {
      if (confirm('Are you sure you want to delete this item and all its children?')) {
        this.deleteNode(node.id);
      }
    });
    actionsMenu.appendChild(deleteBtn);
    content.appendChild(actionsMenu);
    item.appendChild(content);
    if (hasChildren) {
      const children = document.createElement('div');
      children.className = 'tree-children';
      node.children.forEach(child => {
        children.appendChild(this._createTreeItemElement(child));
      });
      item.appendChild(children);
    }
    return item;
  }

  /**
   * เลื่อนโหนดขึ้นหนึ่งตำแหน่งในระดับเดียวกัน
   * @param {number} nodeId - ID ของโหนดที่ต้องการเลื่อน
   * @returns {boolean} - สำเร็จหรือไม่
   */
  moveNodeUp(nodeId) {
    const node = this._findNodeById(nodeId);
    if (!node) return false;
    let siblings;
    let nodeIndex;
    if (!node.parentId) {
      siblings = this.data;
      nodeIndex = siblings.findIndex(item => item.id === nodeId);
    } else {
      const parent = this._findParentNode(nodeId);
      if (!parent || !parent.children) return false;
      siblings = parent.children;
      nodeIndex = siblings.findIndex(item => item.id === nodeId);
    }
    if (nodeIndex <= 0) return false;
    const temp = siblings[nodeIndex];
    siblings[nodeIndex] = siblings[nodeIndex - 1];
    siblings[nodeIndex - 1] = temp;
    this._renderTree();
    this._updateResult();
    return true;
  }

  /**
   * เลื่อนโหนดลงหนึ่งตำแหน่งในระดับเดียวกัน
   * @param {number} nodeId - ID ของโหนดที่ต้องการเลื่อน
   * @returns {boolean} - สำเร็จหรือไม่
   */
  moveNodeDown(nodeId) {
    const node = this._findNodeById(nodeId);
    if (!node) return false;
    let siblings;
    let nodeIndex;
    if (!node.parentId) {
      siblings = this.data;
      nodeIndex = siblings.findIndex(item => item.id === nodeId);
    } else {
      const parent = this._findParentNode(nodeId);
      if (!parent || !parent.children) return false;
      siblings = parent.children;
      nodeIndex = siblings.findIndex(item => item.id === nodeId);
    }
    if (nodeIndex >= siblings.length - 1) return false;
    const temp = siblings[nodeIndex];
    siblings[nodeIndex] = siblings[nodeIndex + 1];
    siblings[nodeIndex + 1] = temp;
    this._renderTree();
    this._updateResult();
    return true;
  }

  /**
   * เลื่อนโหนดไปทางซ้าย (เลื่อนระดับขึ้น)
   * @param {number} nodeId - ID ของโหนดที่ต้องการเลื่อน
   * @returns {boolean} - สำเร็จหรือไม่
   */
  moveNodeLeft(nodeId) {
    const node = this._findNodeById(nodeId);
    if (!node) return false;
    if (!node.parentId) return false;
    const parent = this._findParentNode(nodeId);
    if (!parent) return false;
    const grandparent = this._findParentNode(parent.id);
    const parentIndex = !grandparent
      ? this.data.findIndex(item => item.id === parent.id)
      : grandparent.children.findIndex(item => item.id === parent.id);
    const nodeIndex = parent.children.findIndex(item => item.id === nodeId);
    if (nodeIndex === -1) return false;
    const movedNode = parent.children.splice(nodeIndex, 1)[0];
    movedNode.level = parent.level;
    movedNode.parentId = parent.parentId;
    if (movedNode.children && movedNode.children.length > 0) {
      for (const child of movedNode.children) {
        this._updateNodeLevel(child, movedNode.level + 1);
      }
    }
    if (!grandparent) {
      this.data.splice(parentIndex + 1, 0, movedNode);
    } else {
      grandparent.children.splice(parentIndex + 1, 0, movedNode);
    }
    this._renderTree();
    this._updateResult();
    return true;
  }

  /**
   * เลื่อนโหนดไปทางขวา (เลื่อนระดับลง) - จะเป็นลูกของโหนดที่อยู่ข้างบน
   * @param {number} nodeId - ID ของโหนดที่ต้องการเลื่อน
   * @returns {boolean} - สำเร็จหรือไม่
   */
  moveNodeRight(nodeId) {
    const node = this._findNodeById(nodeId);
    if (!node) return false;
    let siblings;
    let nodeIndex;
    if (!node.parentId) {
      siblings = this.data;
      nodeIndex = siblings.findIndex(item => item.id === nodeId);
    } else {
      const parent = this._findParentNode(nodeId);
      if (!parent || !parent.children) return false;
      siblings = parent.children;
      nodeIndex = siblings.findIndex(item => item.id === nodeId);
    }
    if (nodeIndex <= 0) return false;
    if (this.limitedDepth) {
      const potentialNewLevel = node.level + 1;
      if (potentialNewLevel > this.maxDepth) {
        alert(`Cannot indent: Exceeds maximum depth limit (${this.maxDepth} levels)`);
        return false;
      }
    }
    const newParent = siblings[nodeIndex - 1];
    if (this._isDescendantOf(newParent.id, node)) {
      return false;
    }
    siblings.splice(nodeIndex, 1);
    node.parentId = newParent.id;
    node.level = newParent.level + 1;
    if (node.children && node.children.length > 0) {
      for (const child of node.children) {
        this._updateNodeLevel(child, node.level + 1);
      }
    }
    if (!newParent.children) {
      newParent.children = [];
    }
    newParent.children.push(node);
    this._renderTree();
    this._updateResult();
    return true;
  }

  /**
  * แสดงผลต้นไม้
  * @private
  */
  _renderTree() {
    this.container.innerHTML = '';
    this.data.forEach(node => {
      this.container.appendChild(this._createTreeItemElement(node));
    });
  }

  /**
  * อัพเดตผลลัพธ์ JSON
  * @private
  */
  _updateResult() {
    if (this.outputElement) {
      this.outputElement.textContent = JSON.stringify(this.data, null, 2);
    }
  }

  /**
  * สร้างโครงสร้างเริ่มต้น
  */
  generateInitialStructure() {
    this.data = [
      {
        id: this.nextId++,
        name: 'Item 1',
        level: 1,
        children: [
          {
            id: this.nextId++,
            name: 'Child item 1.1',
            level: 2,
            parentId: 1,
            children: [
              {
                id: this.nextId++,
                name: 'Child item 1.1.1',
                level: 3,
                parentId: 2,
                children: []
              }
            ]
          },
          {
            id: this.nextId++,
            name: 'Child item 1.2',
            level: 2,
            parentId: 1,
            children: []
          }
        ]
      },
      {
        id: this.nextId++,
        name: 'Item 2',
        level: 1,
        children: []
      }
    ];
    this._renderTree();
    this._updateResult();
  }

  /**
  * รับข้อมูลต้นไม้ในรูปแบบ JSON
  * @returns {Array} - ข้อมูลต้นไม้
  */
  getTreeData() {
    return JSON.parse(JSON.stringify(this.data));
  }

  /**
  * กำหนดข้อมูลต้นไม้จาก JSON
  * @param {Array} data - ข้อมูลต้นไม้
  */
  setTreeData(data) {
    this.data = JSON.parse(JSON.stringify(data));
    const findMaxId = (nodes) => {
      let maxId = 0;
      for (const node of nodes) {
        if (node.id > maxId) {
          maxId = node.id;
        }
        if (node.children && node.children.length > 0) {
          const childMaxId = findMaxId(node.children);
          if (childMaxId > maxId) {
            maxId = childMaxId;
          }
        }
      }
      return maxId;
    };
    this.nextId = findMaxId(this.data) + 1;
    this._renderTree();
    this._updateResult();
  }

  /**
  * อัพเดตการตั้งค่า
  * @param {Object} config - การตั้งค่าใหม่
  */
  updateConfig(config) {
    if (config.limitedDepth !== undefined) {
      this.limitedDepth = config.limitedDepth;
    }
    if (config.maxDepth !== undefined) {
      this.maxDepth = config.maxDepth;
    }
    if (config.levelNames !== undefined) {
      this.levelNames = config.levelNames;
    }
    this._renderTree();
  }
}