UNPKG

on-codemerge

Version:

A WYSIWYG editor for on-codemerge is a user-friendly interface that allows users to edit and view their code in real time, exactly as it will appear in the final product

177 lines (176 loc) 11.3 kB
/*! on-codemerge v1.3.1 @author Pavel Kuzmin @license MIT @homepage https://s00d.github.io/on-codemerge/ @repository git+https://github.com/s00d/on-codemerge.git Copyright (c) 2026 Pavel Kuzmin - Built on 2026-07-02T13:39:17.947Z */ var y = Object.defineProperty; var L = (p, e, t) => e in p ? y(p, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : p[e] = t; var s = (p, e, t) => L(p, typeof e != "symbol" ? e + "" : e, t); class M { constructor(e, t, i = {}) { s(this, "menu"); s(this, "editor"); s(this, "activeElement", null); s(this, "subMenus", []); // Для управления вложенными меню s(this, "orientation"); s(this, "buttons", []); s(this, "options"); s(this, "isVisible", !1); s(this, "longPressTimer", null); s(this, "longPressDelay", 500); // Задержка для долгого нажатия в миллисекундах s(this, "touchStartX", 0); s(this, "touchStartY", 0); s(this, "handleTouchStart", (e) => { e.preventDefault(), this.touchStartX = e.touches[0].clientX, this.touchStartY = e.touches[0].clientY, this.longPressTimer = window.setTimeout(() => { this.showContextMenu(e.touches[0].clientX, e.touches[0].clientY); }, this.longPressDelay); }); s(this, "handleTouchMove", (e) => { const t = e.touches[0].clientX, i = e.touches[0].clientY, a = Math.abs(t - this.touchStartX), c = Math.abs(i - this.touchStartY); (a > 10 || c > 10) && this.cancelLongPress(); }); s(this, "handleTouchEnd", (e) => { this.cancelLongPress(); }); s(this, "handleTouchCancel", (e) => { this.cancelLongPress(); }); s(this, "handleClickOutside", (e) => { this.menu.contains(e.target) || this.hide(); }); s(this, "handleEscapeKey", (e) => { e.key === "Escape" && this.hide(); }); this.orientation = i.orientation || "vertical", this.buttons = t, this.editor = e, this.options = { closeOnClickOutside: !0, closeOnEscape: !0, animation: !0, maxHeight: 400, maxWidth: 300, ...i }, this.menu = this.createMenu(), this.editor.getDOMContext().appendChild(this.menu), this.setupEventListeners(); } createMenu() { const e = document.createElement("div"); return e.className = `context-menu ${this.orientation === "horizontal" ? "horizontal" : ""}`, this.options.maxHeight && (e.style.maxHeight = `${this.options.maxHeight}px`), this.options.maxWidth && (e.style.maxWidth = `${this.options.maxWidth}px`), this.buttons.forEach((t) => { if (t.type === "divider") { const i = document.createElement("div"); i.className = `menu-divider ${this.orientation === "horizontal" ? "horizontal" : ""}`, e.appendChild(i); } else if (t.type === "group" && t.groupTitle) { const i = document.createElement("div"); i.className = "menu-group"; const a = document.createElement("div"); a.className = "menu-group-title", a.textContent = t.groupTitle, i.appendChild(a), t.subMenu && t.subMenu.forEach((c) => { const h = this.createMenuItem(c); h && i.appendChild(h); }), e.appendChild(i); } else if (t.type === "custom" && t.customHTML) { const i = document.createElement("div"); i.className = "menu-custom", i.innerHTML = t.customHTML, e.appendChild(i); } else { const i = this.createMenuItem(t); i && e.appendChild(i); } }), e; } createMenuItem(e) { const t = document.createElement("button"); t.className = `menu-item ${e.className || ""}`, t.dataset.action = e.action || "", t.dataset.type = e.type || "button", e.variant && (t.dataset.type = e.variant), e.title && (t.title = e.title); const i = e.icon ? `<span class="menu-icon">${e.icon}</span>` : "", a = e.label || e.title || "", c = a ? `<span class="menu-label">${a}</span>` : "", h = e.hotkey ? `<span class="menu-hotkey">${e.hotkey}</span>` : "", m = e.subMenu ? '<span class="menu-submenu-indicator">▶</span>' : ""; if (e.type === "checkbox" || e.type === "radio") { const o = `<span class="menu-${e.type}"></span>`; t.innerHTML = `${o}${i}${c}${h}`, e.checked && (t.dataset.checked = "true"); } else t.innerHTML = e.iconPosition === "right" ? `${c}${i}${h}${m}` : `${i}${c}${h}${m}`; if (typeof e.disabled == "function" ? t.disabled = e.disabled() : t.disabled = e.disabled || !1, e.onClick && t.addEventListener("click", (o) => { var n; o.preventDefault(), o.stopPropagation(), (n = e.onClick) == null || n.call(e, this.activeElement), e.closeOnClick !== !1 && this.hide(); }), e.subMenu) { const o = new M(this.editor, e.subMenu, { ...this.options, maxHeight: Math.min(this.options.maxHeight || 400, 300) }); this.subMenus.push(o), t.addEventListener("mouseenter", () => { this.subMenus.forEach((u) => { u !== o && u.hide(); }); const n = t.getBoundingClientRect(), v = n.right + 5, f = n.top; o.show(t, v, f); }), t.addEventListener("mouseleave", (n) => { setTimeout(() => { const v = document.elementFromPoint(n.clientX, n.clientY), f = o.menu.contains(v), u = o.menu.contains(n.relatedTarget), l = o.menu.getBoundingClientRect(), r = t.getBoundingClientRect(), d = { left: Math.min(r.right, l.left) - 10, right: Math.max(r.right, l.left) + 10, top: Math.min(r.top, l.top) - 10, bottom: Math.max(r.bottom, l.bottom) + 10 }, E = n.clientX >= d.left && n.clientX <= d.right && n.clientY >= d.top && n.clientY <= d.bottom, g = n.clientY >= Math.min(r.top, l.top) - 20 && n.clientY <= Math.max(r.bottom, l.bottom) + 20; !f && !u && !E && !g && o.hide(); }, 50); }), o.menu.addEventListener("mouseenter", () => { }), o.menu.addEventListener("mouseleave", (n) => { setTimeout(() => { const v = document.elementFromPoint(n.clientX, n.clientY), f = t.contains(v), u = t.contains(n.relatedTarget), l = o.menu.getBoundingClientRect(), r = t.getBoundingClientRect(), d = { left: Math.min(r.right, l.left) - 10, right: Math.max(r.right, l.left) + 10, top: Math.min(r.top, l.top) - 10, bottom: Math.max(r.bottom, l.bottom) + 10 }, E = n.clientX >= d.left && n.clientX <= d.right && n.clientY >= d.top && n.clientY <= d.bottom, g = n.clientY >= Math.min(r.top, l.top) - 20 && n.clientY <= Math.max(r.bottom, l.bottom) + 20; !f && !u && !E && !g && o.hide(); }, 50); }); } return t; } setupEventListeners() { this.options.closeOnClickOutside && this.editor.getDOMContext().addEventListener("click", this.handleClickOutside), this.options.closeOnEscape && this.editor.getDOMContext().addEventListener("keydown", this.handleEscapeKey); let e = 0, t = 0; this.menu.addEventListener("touchstart", (i) => { e = i.touches[0].clientY, t = i.touches[0].clientX; }), this.menu.addEventListener("touchmove", (i) => { i.preventDefault(); }), this.menu.addEventListener("touchend", (i) => { const a = i.changedTouches[0].clientY, c = i.changedTouches[0].clientX, h = Math.abs(a - e), m = Math.abs(c - t); (h > 50 || m > 50) && this.hide(); }); } setupLongPressListeners() { this.activeElement && (this.activeElement.removeEventListener("touchstart", this.handleTouchStart), this.activeElement.removeEventListener("touchmove", this.handleTouchMove), this.activeElement.removeEventListener("touchend", this.handleTouchEnd), this.activeElement.removeEventListener("touchcancel", this.handleTouchCancel)), this.activeElement && (this.activeElement.addEventListener("touchstart", this.handleTouchStart), this.activeElement.addEventListener("touchmove", this.handleTouchMove), this.activeElement.addEventListener("touchend", this.handleTouchEnd), this.activeElement.addEventListener("touchcancel", this.handleTouchCancel)); } cancelLongPress() { this.longPressTimer && (clearTimeout(this.longPressTimer), this.longPressTimer = null); } showContextMenu(e, t) { this.activeElement && this.show(this.activeElement, e, t); } show(e, t, i) { this.activeElement = e, this.isVisible = !0, this.setupLongPressListeners(), this.menu.style.display = "flex", this.menu.style.flexDirection = this.orientation === "horizontal" ? "row" : "column", this.options.animation && (this.menu.style.opacity = "0", this.menu.style.transform = "translateY(-10px) scale(0.95)"), this.menu.style.left = `${t}px`, this.menu.style.top = `${i}px`, this.options.animation && requestAnimationFrame(() => { this.menu.style.opacity = "1", this.menu.style.transform = "translateY(0) scale(1)"; }), requestAnimationFrame(() => { this.adjustPosition(); }); } adjustPosition() { const e = this.menu.getBoundingClientRect(), t = window.innerWidth, i = window.innerHeight, a = parseInt(this.menu.style.left), c = parseInt(this.menu.style.top); let h = a, m = c; e.right > t && (h = a - (e.right - t) - 10), e.left < 0 && (h = 10), e.bottom > i && (m = c - (e.bottom - i) - 10), e.top < 0 && (m = 10), (h !== a || m !== c) && (this.menu.style.left = `${h}px`, this.menu.style.top = `${m}px`); } hide() { this.isVisible && (this.isVisible = !1, this.cancelLongPress(), this.activeElement && (this.activeElement.removeEventListener("touchstart", this.handleTouchStart), this.activeElement.removeEventListener("touchmove", this.handleTouchMove), this.activeElement.removeEventListener("touchend", this.handleTouchEnd), this.activeElement.removeEventListener("touchcancel", this.handleTouchCancel)), this.subMenus.forEach((e) => e.hide()), this.options.animation ? (this.menu.style.opacity = "0", this.menu.style.transform = "translateY(-10px) scale(0.95)", setTimeout(() => { this.menu.style.display = "none", this.activeElement = null; }, 200)) : (this.menu.style.display = "none", this.activeElement = null)); } updateButtons(e) { this.menu.innerHTML = "", this.buttons = e, this.menu.appendChild(this.createMenu()); } updateButtonState(e, t) { const i = this.menu.querySelector(`[data-action="${e}"]`); i && (Object.assign(i.dataset, t), t.disabled !== void 0 && (i.disabled = typeof t.disabled == "function" ? t.disabled() : t.disabled), t.checked !== void 0 && (i.dataset.checked = t.checked.toString())); } getElement() { return this.menu; } destroy() { this.cancelLongPress(), this.activeElement && (this.activeElement.removeEventListener("touchstart", this.handleTouchStart), this.activeElement.removeEventListener("touchmove", this.handleTouchMove), this.activeElement.removeEventListener("touchend", this.handleTouchEnd), this.activeElement.removeEventListener("touchcancel", this.handleTouchCancel)), this.options.closeOnClickOutside && this.editor.getDOMContext().removeEventListener("click", this.handleClickOutside), this.options.closeOnEscape && this.editor.getDOMContext().removeEventListener("keydown", this.handleEscapeKey), this.subMenus.forEach((e) => e.destroy()), this.menu.parentNode && this.menu.parentNode.removeChild(this.menu); } } export { M as ContextMenu };