import { safeCustomElement } from "../../shared/safe-custom-element.js";
import { TailwindStyledElement } from "../../shared/tailwind-element/index.js";
import { __decorate } from "../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.js";
import "../lukso-icon/index.js";
import "../lukso-sanitize/index.js";
import { cn } from "../../shared/tools/cn.js";
import { checkAccessibility, formatViolationsForTooltip } from "../../shared/tools/accessibility-checker.js";
import "../../shared/tools/index.js";
import "../lukso-button/index.js";
import "../lukso-form-description/index.js";
import "../lukso-form-label/index.js";
import "../lukso-dropdown/index.js";
import "../lukso-dropdown-option/index.js";
import "../lukso-markdown/index.js";
import style_default from "./style.css?inline.js";
import "../lukso-textarea/index.js";
import "../lukso-switch/index.js";
import "../lukso-tooltip/index.js";
import { html, nothing } from "lit";
import { property, query, state } from "lit/decorators.js";
import { tv } from "tailwind-variants";
//#region src/components/lukso-markdown-editor/index.ts
var DEFAULT_PREVIEW_BACKGROUND_COLOR = "#f9f9f9";
var DEFAULT_PREVIEW_TEXT_COLOR = "#243542";
var ACCESSIBILITY_CHECK_DELAY = 1e3;
var LuksoMarkdownEditor = class LuksoMarkdownEditor extends TailwindStyledElement(style_default) {
	constructor(..._args) {
		super(..._args);
		this.value = "";
		this.name = "";
		this.label = "";
		this.description = "";
		this.error = "";
		this.isFullWidth = false;
		this.isReadonly = false;
		this.isDisabled = false;
		this.isNonResizable = false;
		this.autofocus = false;
		this.size = "large";
		this.isPreview = false;
		this.rows = 6;
		this.placeholder = "";
		this.previewBackgroundColor = DEFAULT_PREVIEW_BACKGROUND_COLOR;
		this.previewTextColor = DEFAULT_PREVIEW_TEXT_COLOR;
		this.stripHtmlTags = false;
		this.tools = void 0;
		this.toolsState = [];
		this.defaultTools = [
			"heading",
			"bold",
			"italic",
			"list",
			"link",
			"alignment",
			"color"
		];
		this.savedSelectionForPreview = null;
		this.isHeadingDropdownOpen = false;
		this.isColorDropdownOpen = false;
		this.isListDropdownOpen = false;
		this.isAlignmentDropdownOpen = false;
		this.headingTriggerId = "heading-dropdown-trigger";
		this.colorTriggerId = "color-dropdown-trigger";
		this.listTriggerId = "list-dropdown-trigger";
		this.alignmentTriggerId = "alignment-dropdown-trigger";
		this.currentSelection = {
			start: 0,
			end: 0
		};
		this.savedSelection = null;
		this.activeFormats = {
			bold: false,
			italic: false,
			link: false,
			h1: false,
			h2: false,
			h3: false,
			h4: false,
			color: false,
			activeColor: DEFAULT_PREVIEW_TEXT_COLOR,
			unorderedList: false,
			orderedList: false,
			alignment: "left"
		};
		this.undoStack = [];
		this.redoStack = [];
		this.isUndoRedoAction = false;
		this.lastSavedValue = "";
		this.undoTimeout = null;
		this.MAX_UNDO_STACK_SIZE = 100;
		this.UNDO_SAVE_DELAY = 500;
		this.accessibilityViolations = [];
		this.hasAccessibilityViolations = false;
		this.accessibilityCheckTimeout = null;
		this.handleOutsideClick = (event) => {
			const target = event.target;
			if (!(this.contains(target) || this.shadowRoot?.contains(target))) {
				if (this.isHeadingDropdownOpen) this.isHeadingDropdownOpen = false;
				if (this.isColorDropdownOpen) this.isColorDropdownOpen = false;
				if (this.isListDropdownOpen) this.isListDropdownOpen = false;
				if (this.isAlignmentDropdownOpen) this.isAlignmentDropdownOpen = false;
				return;
			}
			const isInsideHeadingDropdown = this.shadowRoot?.getElementById("headingDropdown")?.contains(target);
			const isInsideColorDropdown = this.shadowRoot?.getElementById("colorDropdown")?.contains(target);
			const isInsideListDropdown = this.shadowRoot?.getElementById("listDropdown")?.contains(target);
			const isInsideAlignmentDropdown = this.shadowRoot?.getElementById("alignmentDropdown")?.contains(target);
			const isHeadingTrigger = this.shadowRoot?.getElementById(this.headingTriggerId)?.contains(target);
			const isColorTrigger = this.shadowRoot?.getElementById(this.colorTriggerId)?.contains(target);
			const isListTrigger = this.shadowRoot?.getElementById(this.listTriggerId)?.contains(target);
			const isAlignmentTrigger = this.shadowRoot?.getElementById(this.alignmentTriggerId)?.contains(target);
			if (!isInsideHeadingDropdown && !isHeadingTrigger && this.isHeadingDropdownOpen) this.isHeadingDropdownOpen = false;
			if (!isInsideColorDropdown && !isColorTrigger && this.isColorDropdownOpen) this.isColorDropdownOpen = false;
			if (!isInsideListDropdown && !isListTrigger && this.isListDropdownOpen) this.isListDropdownOpen = false;
			if (!isInsideAlignmentDropdown && !isAlignmentTrigger && this.isAlignmentDropdownOpen) this.isAlignmentDropdownOpen = false;
		};
		this.styles = tv({
			slots: {
				wrapper: "w-[inherit]",
				header: "flex items-center justify-between gap-2 border border-neutral-90 rounded-12 px-3 py-2 bg-neutral-100",
				toolbar: "flex flex-wrap items-center gap-1",
				area: "relative",
				editor: "",
				preview: "p-3 border border-neutral-90 rounded-12 min-h-[158px]",
				colorMenu: "relative",
				headingMenu: "relative",
				listMenu: "relative",
				alignmentMenu: "relative",
				label: "heading-inter-14-bold text-neutral-20",
				description: "paragraph-inter-12-regular text-neutral-20",
				divider: "w-[1px] h-4 bg-neutral-90"
			},
			variants: { isFullWidth: {
				true: { wrapper: "w-full" },
				false: { wrapper: "w-[350px]" }
			} },
			compoundVariants: [{
				isFullWidth: false,
				class: { wrapper: "w-[500px]" }
			}]
		});
		this.toolbarButton = tv({
			base: "hover:bg-neutral-95 transition border-0 !shadow-none",
			variants: {
				active: { true: "bg-neutral-95" },
				disabled: { true: "opacity-50 cursor-not-allowed" }
			}
		});
		this.colorSamples = [
			"#FFE5E5",
			"#FFB3B3",
			"#FF8080",
			"#FF6666",
			"#FF4D4D",
			"#E63946",
			"#FFEBD5",
			"#FFD1A3",
			"#FFB870",
			"#FFA54D",
			"#FF944D",
			"#FF7F11",
			"#FFFBD5",
			"#FFF3A3",
			"#FFE870",
			"#FFDD4D",
			"#FFD633",
			"#F4C430",
			"#E6F9EC",
			"#B3E6C1",
			"#80D499",
			"#66CC80",
			"#4DB870",
			"#2D9C5B",
			"#E6F3FF",
			"#B3DAFF",
			"#80C2FF",
			"#66B2FF",
			"#4DA6FF",
			"#3A86FF",
			"#F3E6FF",
			"#D9B3FF",
			"#BF80FF",
			"#A64DFF",
			"#9333FF",
			"#7B2CBF",
			"#FFE6F3",
			"#FFB3D9",
			"#FF80BF",
			"#FF66B2",
			"#FF4DA6",
			"#E75480",
			"#F5E6DA",
			"#E6CBB3",
			"#D9B38C",
			"#CC9966",
			"#B3773A",
			"#8B5E3C",
			"#FFFFFF",
			"#F5F5F5",
			"#E0E0E0",
			"#BDBDBD",
			"#757575",
			"#000000",
			DEFAULT_PREVIEW_TEXT_COLOR
		];
		this.handleTextareaInput = (event) => {
			const newValue = event.detail?.value || "";
			if (!this.isUndoRedoAction) this.scheduleUndoStateSave();
			this.value = newValue;
			this.emitChangeAndRefresh(event);
		};
		this.handleTextareaKeyUp = () => {
			requestAnimationFrame(() => this.updateActiveFormats());
		};
		this.handleTextareaClick = () => {
			requestAnimationFrame(() => this.updateActiveFormats());
		};
		this.handleKeyDown = (event) => {
			const isUndo = (event.metaKey || event.ctrlKey) && event.key === "z" && !event.shiftKey;
			const isRedo = (event.metaKey || event.ctrlKey) && (event.key === "y" || event.key === "z" && event.shiftKey);
			if (isUndo) {
				event.preventDefault();
				this.undo();
			} else if (isRedo) {
				event.preventDefault();
				this.redo();
			} else if (event.key === "Enter" && !event.metaKey && !event.ctrlKey && !event.shiftKey) {
				if (this.handleListContinuation()) event.preventDefault();
			} else if (event.key === "Backspace" && !event.metaKey && !event.ctrlKey && !event.shiftKey) {
				if (this.handleListBackspace()) event.preventDefault();
			} else if (event.key === "Tab" && !event.metaKey && !event.ctrlKey) {
				if (event.shiftKey) {
					if (this.handleListOutdent()) event.preventDefault();
				} else if (this.handleListIndent()) event.preventDefault();
			}
		};
	}
	dispatchChange(event) {
		this.updateComplete.then(() => {
			const changeEvent = new CustomEvent("on-markdown-change", {
				detail: {
					value: this.value,
					event
				},
				bubbles: false,
				composed: true
			});
			this.dispatchEvent(changeEvent);
		});
	}
	/**
	* Check accessibility violations on the live preview content
	*/
	async performAccessibilityCheck() {
		if (!this.value.trim()) {
			this.accessibilityViolations = [];
			this.hasAccessibilityViolations = false;
			return;
		}
		try {
			if (this.isPreview) {
				const markdownEl = this.shadowRoot?.querySelector("lukso-markdown");
				if (markdownEl) {
					await new Promise((resolve) => setTimeout(resolve, 200));
					const proseDiv = (markdownEl.shadowRoot?.querySelector("lukso-sanitize"))?.shadowRoot?.querySelector("div.prose");
					if (proseDiv) {
						const result = await checkAccessibility(proseDiv);
						this.accessibilityViolations = result.violations;
						this.hasAccessibilityViolations = result.hasViolations;
					} else {
						this.accessibilityViolations = [];
						this.hasAccessibilityViolations = false;
					}
				}
			} else {
				this.accessibilityViolations = [];
				this.hasAccessibilityViolations = false;
			}
		} catch (error) {
			console.warn("Accessibility checking failed:", error);
			this.accessibilityViolations = [];
			this.hasAccessibilityViolations = false;
		}
		this.requestUpdate();
	}
	/**
	* Schedule accessibility check with debouncing
	*/
	scheduleAccessibilityCheck() {
		if (this.accessibilityCheckTimeout) clearTimeout(this.accessibilityCheckTimeout);
		this.accessibilityCheckTimeout = window.setTimeout(() => {
			this.performAccessibilityCheck();
			this.accessibilityCheckTimeout = null;
		}, ACCESSIBILITY_CHECK_DELAY);
	}
	/**
	* Clean up empty color spans from the value.
	* This removes spans like <span style="color: #xxx"></span> with no content.
	*/
	cleanupEmptyColorSpans() {
		const cleanedValue = this.value.replace(/<span style="color: [^"]+"><\/span>/g, "");
		if (cleanedValue !== this.value) {
			this.value = cleanedValue;
			const textarea = this.textareaEl?.shadowRoot?.querySelector("textarea");
			if (textarea) {
				const currentPos = textarea.selectionStart ?? 0;
				textarea.value = cleanedValue;
				const newPos = Math.min(currentPos, cleanedValue.length);
				textarea.setSelectionRange(newPos, newPos);
			}
		}
	}
	/**
	* Unified helper that ensures both active format state and change events are properly
	* emitted after any value mutation. This replaces the scattered updateActiveFormats()
	* and dispatchChange() calls throughout the codebase.
	*
	* @param event - Optional event that triggered the change
	*/
	emitChangeAndRefresh(event) {
		this.cleanupEmptyColorSpans();
		this.updateActiveFormats();
		this.dispatchChange(event);
		this.scheduleAccessibilityCheck();
	}
	/**
	* Utility to perform an operation with the current textarea selection.
	*
	* @param callback -
	*/
	withSelection(callback) {
		const textarea = this.textareaEl?.shadowRoot?.querySelector("textarea");
		if (!textarea) return;
		const start = textarea.selectionStart ?? 0;
		const end = textarea.selectionEnd ?? 0;
		const value = this.value;
		callback(textarea, start, end, value);
		textarea.focus();
	}
	/**
	* Expand empty selection to current word boundaries
	*
	* @param start - selection start
	* @param end - selection end
	* @param value - full textarea value
	*/
	expandSelectionToWord(start, end, value) {
		if (start !== end) return {
			start,
			end
		};
		const charBefore = start > 0 ? value[start - 1] : "";
		const charAfter = start < value.length ? value[start] : "";
		if (charBefore === "(" && charAfter === ")" || charBefore === "[" && charAfter === "]") return {
			start,
			end
		};
		let _start = start;
		let _end = end;
		const isWord = (ch) => /[\p{L}\p{N}_-]/u.test(ch);
		while (_start > 0 && isWord(value[_start - 1])) _start--;
		while (_end < value.length && isWord(value[_end])) _end++;
		return {
			start: _start,
			end: _end
		};
	}
	/**
	* Apply or toggle heading formatting for the current line(s).
	*
	* @param level - 0 to remove heading, 1-4 for heading levels
	*/
	applyHeading(level) {
		if (this.isReadonly || this.isDisabled) return;
		this.saveUndoStateBeforeChange();
		const desiredPrefix = level > 0 ? `${"#".repeat(level)} ` : "";
		this.withSelection((textarea, start, end, value) => {
			const lineStart = value.lastIndexOf("\n", start - 1) + 1;
			let lineEnd = value.indexOf("\n", end);
			if (lineEnd === -1) lineEnd = value.length;
			const before = value.slice(0, lineStart);
			const selected = value.slice(lineStart, lineEnd);
			const after = value.slice(lineEnd);
			const lines = selected.split("\n");
			const headingRegex = /^(#{1,6})\s+/;
			const allAlreadyLevel = lines.every((l) => l.startsWith(desiredPrefix));
			let transformed;
			if (level === 0) transformed = lines.map((l) => l.replace(headingRegex, "")).join("\n");
			else transformed = lines.map((l) => {
				const withoutAny = l.replace(headingRegex, "");
				if (allAlreadyLevel) return withoutAny;
				return desiredPrefix + withoutAny;
			}).join("\n");
			this.value = before + transformed + after;
			textarea.value = before + transformed + after;
			let cursorPosition = before.length;
			if (level > 0 && !allAlreadyLevel) if ((lines[0] || "").replace(headingRegex, "").trim()) {
				const firstLineEnd = transformed.indexOf("\n");
				cursorPosition = before.length + (firstLineEnd === -1 ? transformed.length : firstLineEnd);
			} else cursorPosition = before.length + desiredPrefix.length;
			else if (level === 0 || allAlreadyLevel) {
				const firstLineEnd = transformed.indexOf("\n");
				cursorPosition = before.length + (firstLineEnd === -1 ? transformed.length : firstLineEnd);
			}
			requestAnimationFrame(() => {
				textarea.setSelectionRange(cursorPosition, cursorPosition);
				this.emitChangeAndRefresh();
			});
		});
	}
	/**
	* Apply or toggle list formatting for the current line(s).
	*
	* @param listType - 'none' to remove list, 'unordered' for bullet list, 'ordered' for numbered list
	*/
	applyList(listType) {
		if (this.isReadonly || this.isDisabled) return;
		this.saveUndoStateBeforeChange();
		this.withSelection((textarea, start, end, value) => {
			const lineStart = value.lastIndexOf("\n", start - 1) + 1;
			let lineEnd = value.indexOf("\n", end);
			if (lineEnd === -1) lineEnd = value.length;
			const before = value.slice(0, lineStart);
			const selected = value.slice(lineStart, lineEnd);
			const after = value.slice(lineEnd);
			const lines = selected.split("\n");
			const unorderedRegex = /^(\s*)[-*+]\s+/;
			const orderedRegex = /^(\s*)\d+\.\s+/;
			let transformed;
			if (listType === "none") transformed = lines.map((l) => {
				if (unorderedRegex.test(l)) return l.replace(unorderedRegex, "$1");
				if (orderedRegex.test(l)) return l.replace(orderedRegex, "$1");
				return l;
			}).join("\n");
			else if (listType === "unordered") if (lines.every((l) => l.trim() === "" || unorderedRegex.test(l)) && lines.some((l) => unorderedRegex.test(l))) transformed = lines.map((l) => {
				if (unorderedRegex.test(l)) return l.replace(unorderedRegex, "$1");
				return l;
			}).join("\n");
			else transformed = lines.map((l) => {
				if (l.trim() === "") return l;
				let cleaned = l;
				if (unorderedRegex.test(l)) cleaned = l.replace(unorderedRegex, "$1");
				else if (orderedRegex.test(l)) cleaned = l.replace(orderedRegex, "$1");
				const indentMatch = cleaned.match(/^(\s*)/);
				return `${indentMatch ? indentMatch[1] : ""}- ${cleaned.replace(/^\s*/, "")}`;
			}).join("\n");
			else if (listType === "ordered") if (lines.every((l) => l.trim() === "" || orderedRegex.test(l)) && lines.some((l) => orderedRegex.test(l))) transformed = lines.map((l) => {
				if (orderedRegex.test(l)) return l.replace(orderedRegex, "$1");
				return l;
			}).join("\n");
			else {
				let counter = 1;
				const beforeLines = before.split("\n");
				for (let i = beforeLines.length - 1; i >= 0; i--) {
					const line = beforeLines[i];
					const orderedMatch = line.match(/^(\s*)(\d+)\.\s+(.*)$/);
					if (orderedMatch) {
						counter = parseInt(orderedMatch[2], 10) + 1;
						break;
					} else if (line.trim() !== "" && !line.match(/^\s*[-*+]\s+/)) break;
				}
				transformed = lines.map((l) => {
					if (l.trim() === "") return l;
					let cleaned = l;
					if (unorderedRegex.test(l)) cleaned = l.replace(unorderedRegex, "$1");
					else if (orderedRegex.test(l)) cleaned = l.replace(orderedRegex, "$1");
					const indentMatch = cleaned.match(/^(\s*)/);
					const indent = indentMatch ? indentMatch[1] : "";
					const content = cleaned.replace(/^\s*/, "");
					return `${indent}${counter++}. ${content}`;
				}).join("\n");
			}
			let finalValue = before + transformed + after;
			if (listType === "ordered") {
				if (!lines.every((l) => l.trim() === "" || orderedRegex.test(l)) || !lines.some((l) => orderedRegex.test(l))) {
					const transformedLines = transformed.split("\n");
					const indentMatch = transformedLines[transformedLines.length - 1].match(/^(\s*)/);
					const indent = indentMatch ? indentMatch[1] : "";
					const endPosition = before.length + transformed.length;
					finalValue = this.renumberOrderedListItems(finalValue, endPosition, indent);
				}
			}
			if (listType === "none") {
				if (lines.some((l) => orderedRegex.test(l))) finalValue = this.renumberSubsequentOrderedLists(finalValue, before.length + transformed.length);
			}
			this.value = finalValue;
			textarea.value = finalValue;
			const firstLineEnd = transformed.indexOf("\n");
			const cursorPosition = before.length + (firstLineEnd === -1 ? transformed.length : firstLineEnd);
			requestAnimationFrame(() => {
				textarea.setSelectionRange(cursorPosition, cursorPosition);
				this.emitChangeAndRefresh();
			});
		});
	}
	/**
	* Get the current active list type based on activeFormats.
	*/
	getActiveListType() {
		if (this.activeFormats.unorderedList) return "unordered";
		if (this.activeFormats.orderedList) return "ordered";
		return "none";
	}
	/**
	* Get the current alignment icon name based on activeFormats.
	*/
	getAlignmentIcon() {
		switch (this.activeFormats.alignment) {
			case "center": return "textalign-center";
			case "right": return "textalign-right";
			default: return "textalign-left";
		}
	}
	/**
	* Apply or toggle text alignment for the current line(s).
	*
	* @param alignment - 'left', 'center', or 'right'
	*/
	applyAlignment(alignment) {
		if (this.isReadonly || this.isDisabled) return;
		this.saveUndoStateBeforeChange();
		this.withSelection((textarea, start, end, value) => {
			const lineStart = value.lastIndexOf("\n", start - 1) + 1;
			let lineEnd = value.indexOf("\n", end);
			if (lineEnd === -1) lineEnd = value.length;
			const before = value.slice(0, lineStart);
			const selected = value.slice(lineStart, lineEnd);
			const after = value.slice(lineEnd);
			let transformed;
			const existingMatch = selected.match(/<div style="text-align: (left|center|right);">(.*?)<\/div>/s);
			if (existingMatch || this.hasNestedAlignment(selected)) if (existingMatch) {
				const existingAlignment = existingMatch[1];
				const innerContent = existingMatch[2];
				if (existingAlignment === alignment) transformed = innerContent;
				else transformed = `<div style="text-align: ${alignment};">${innerContent}</div>`;
			} else if (this.getNestedAlignment(selected) === alignment) transformed = this.removeNestedAlignment(selected);
			else transformed = this.replaceNestedAlignment(selected, alignment);
			else if (alignment === "left") transformed = selected;
			else transformed = this.wrapContentWithAlignment(selected, alignment);
			this.value = before + transformed + after;
			textarea.value = before + transformed + after;
			let selStart;
			let selEnd;
			if (alignment === "left" || existingMatch && existingMatch[1] === alignment || this.getNestedAlignment(selected) === alignment) {
				selStart = before.length;
				selEnd = before.length + transformed.length;
			} else {
				const openTagMatch = transformed.match(/<div style="text-align: (center|right);">([^<]*)<\/div>/);
				if (openTagMatch) {
					const innerContent = openTagMatch[2];
					const tagStart = transformed.indexOf(">") + 1;
					selStart = before.length + tagStart;
					selEnd = selStart + innerContent.length;
				} else {
					selStart = before.length;
					selEnd = before.length + transformed.length;
				}
			}
			requestAnimationFrame(() => {
				textarea.setSelectionRange(selStart, selEnd);
				this.emitChangeAndRefresh();
			});
		});
	}
	/**
	* Check if content has nested alignment divs inside formatting markers.
	*/
	hasNestedAlignment(content) {
		return /<div style="text-align: (left|center|right);">/.test(content);
	}
	/**
	* Get the alignment from nested alignment divs.
	*/
	getNestedAlignment(content) {
		const alignmentMatch = content.match(/<div style="text-align: (left|center|right);">/);
		return alignmentMatch ? alignmentMatch[1] : null;
	}
	/**
	* Remove nested alignment divs from content.
	*/
	removeNestedAlignment(content) {
		return content.replace(/<div style="text-align: (left|center|right);">([^<]*?)<\/div>/g, "$2");
	}
	/**
	* Replace nested alignment with a new alignment.
	*/
	replaceNestedAlignment(content, newAlignment) {
		return content.replace(/<div style="text-align: (left|center|right);">([^<]*?)<\/div>/g, `<div style="text-align: ${newAlignment};">$2</div>`);
	}
	/**
	* Wrap content with alignment div, ensuring proper nesting inside formatting markers.
	* Examples:
	* - **text** becomes **<div style="text-align: center;">text</div>**
	* - *text* becomes *<div style="text-align: center;">text</div>*
	* - # text becomes # <div style="text-align: center;">text</div>
	*
	* @param content - the content to wrap
	* @param alignment - 'left', 'center' or 'right'
	*/
	wrapContentWithAlignment(content, alignment) {
		const alignmentDiv = (innerContent) => `<div style="text-align: ${alignment};">${innerContent}</div>`;
		const headingMatch = content.match(/^(#{1,6}\s+)(.*)$/);
		if (headingMatch) {
			const headingPrefix = headingMatch[1];
			const headingText = headingMatch[2];
			return headingPrefix + alignmentDiv(headingText);
		}
		const boldMatch = content.match(/^(\*\*)(.+?)(\*\*)$/s);
		if (boldMatch) {
			const innerText = boldMatch[2];
			return `**${alignmentDiv(innerText)}**`;
		}
		const italicMatch = content.match(/^(\*)(.+?)(\*)$/s);
		if (italicMatch) {
			const innerText = italicMatch[2];
			return `*${alignmentDiv(innerText)}*`;
		}
		const linkMatch = content.match(/^(\[)(.+?)(\]\([^)]+\))$/s);
		if (linkMatch) {
			const linkStart = linkMatch[1];
			const linkText = linkMatch[2];
			const linkEnd = linkMatch[3];
			return linkStart + alignmentDiv(linkText) + linkEnd;
		}
		const colorMatch = content.match(/^(<span style="color: [^"]+;">)(.+?)(<\/span>)$/s);
		if (colorMatch) {
			const colorStart = colorMatch[1];
			const colorText = colorMatch[2];
			const colorEnd = colorMatch[3];
			return colorStart + alignmentDiv(colorText) + colorEnd;
		}
		const listMatch = content.match(/^(\s*(?:[-*+]|\d+\.)\s+)(.*)$/);
		if (listMatch) {
			const listPrefix = listMatch[1];
			const listText = listMatch[2];
			return listPrefix + alignmentDiv(listText);
		}
		return alignmentDiv(content);
	}
	/**
	* Toggle inline formatting by wrapping/unwrapping selection or current word.
	*
	* @param wrapper - the markdown wrapper to toggle, e.g. '**' for bold, '*' for italic
	*/
	toggleWrap(wrapper) {
		if (this.isReadonly || this.isDisabled) return;
		this.saveUndoStateBeforeChange();
		this.withSelection((textarea, start, end, value) => {
			if (this.isSelectionInLinkUrl(start, end, value)) return;
			const { start: s, end: e } = this.expandSelectionToWord(start, end, value);
			let before = value.slice(0, s);
			let selected = value.slice(s, e);
			let after = value.slice(e);
			if (before.endsWith(wrapper) && after.startsWith(wrapper)) {
				before = before.slice(0, before.length - wrapper.length);
				after = after.slice(wrapper.length);
				this.value = before + selected + after;
				textarea.value = before + selected + after;
				const selStart = before.length;
				const selEnd = selStart + selected.length;
				requestAnimationFrame(() => {
					textarea.setSelectionRange(selStart, selEnd);
					this.emitChangeAndRefresh();
				});
				return;
			}
			if (selected.startsWith(wrapper) && selected.endsWith(wrapper)) {
				selected = selected.slice(wrapper.length, selected.length - wrapper.length);
				this.value = before + selected + after;
				textarea.value = before + selected + after;
				const selStart = before.length;
				const selEnd = selStart + selected.length;
				requestAnimationFrame(() => {
					textarea.setSelectionRange(selStart, selEnd);
					this.emitChangeAndRefresh();
				});
				return;
			}
			const wrapped = `${wrapper}${selected || ""}${wrapper}`;
			this.value = before + wrapped + after;
			textarea.value = before + wrapped + after;
			const selStart = before.length + wrapper.length;
			const selEnd = selStart + (selected ? selected.length : 0);
			requestAnimationFrame(() => {
				textarea.setSelectionRange(selStart, selEnd);
				this.emitChangeAndRefresh();
			});
		});
	}
	/**
	* Toggle preview mode on or off.
	*/
	togglePreview() {
		if (this.isPreview) this.exitPreview();
		else this.enterPreview();
		this.isPreview = !this.isPreview;
		if (this.isPreview) requestAnimationFrame(() => {
			this.scheduleAccessibilityCheck();
		});
	}
	/**
	* Enter preview mode - save current state and remove keyboard listeners from textarea
	*/
	enterPreview() {
		const textarea = this.textareaEl?.shadowRoot?.querySelector("textarea");
		if (textarea) {
			this.savedSelectionForPreview = {
				start: textarea.selectionStart ?? 0,
				end: textarea.selectionEnd ?? 0
			};
			this.removeKeyboardListeners();
		}
	}
	/**
	* Exit preview mode - restore state and reattach keyboard listeners
	*/
	exitPreview() {
		requestAnimationFrame(() => {
			const textarea = this.textareaEl?.shadowRoot?.querySelector("textarea");
			if (textarea && this.savedSelectionForPreview) {
				textarea.focus();
				textarea.setSelectionRange(this.savedSelectionForPreview.start, this.savedSelectionForPreview.end);
				this.currentSelection = this.savedSelectionForPreview;
				this.savedSelectionForPreview = null;
				this.updateActiveFormats();
			}
			this.addKeyboardListeners();
		});
	}
	/**
	* Insert or edit a markdown link [text](url).
	*/
	insertLink() {
		if (this.isReadonly || this.isDisabled) return;
		this.saveUndoStateBeforeChange();
		const placeholderText = "link text";
		const placeholderUrl = "";
		this.withSelection((textarea, start, end, value) => {
			if (this.isSelectionInLinkUrl(start, end, value) || this.isSelectionInLinkText(start, end, value)) {
				const leftBracket = value.lastIndexOf("[", start);
				const rightParen = value.indexOf(")", Math.max(start, end));
				const rightBracket = value.indexOf("]", leftBracket);
				const openParen = value.indexOf("(", rightBracket);
				if (leftBracket !== -1 && rightBracket !== -1 && openParen !== -1 && rightParen !== -1) {
					const match = value.slice(leftBracket, rightParen + 1).match(/^\[([^\]]+)\]\(([^)]*)\)$/);
					if (match) {
						const textOnly = match[1];
						this.value = value.slice(0, leftBracket) + textOnly + value.slice(rightParen + 1);
						textarea.value = value.slice(0, leftBracket) + textOnly + value.slice(rightParen + 1);
						const newCursor = leftBracket + textOnly.length;
						requestAnimationFrame(() => {
							textarea.setSelectionRange(newCursor, newCursor);
							this.emitChangeAndRefresh();
						});
						return;
					}
				}
				return;
			}
			const { start: s, end: e } = this.expandSelectionToWord(start, end, value);
			const before = value.slice(0, s);
			const selected = value.slice(s, e);
			const after = value.slice(e);
			const linkRegex = /^\[([^\]]+)\]\(([^)]+)\)$/;
			if (linkRegex.test(selected)) {
				const textOnly = selected.match(linkRegex)?.[1] ?? selected;
				this.value = before + textOnly + after;
				textarea.value = before + textOnly + after;
				const newStart = before.length;
				const newEnd = newStart + textOnly.length;
				requestAnimationFrame(() => {
					textarea.setSelectionRange(newStart, newEnd);
					this.emitChangeAndRefresh();
				});
				return;
			}
			const leftBracket = value.lastIndexOf("[", s);
			const rightParen = value.indexOf(")", e);
			const rightBracket = value.indexOf("]", e);
			const openParen = value.indexOf("(", e);
			if (leftBracket !== -1 && rightBracket !== -1 && openParen !== -1 && rightParen !== -1 && leftBracket < rightBracket && rightBracket < openParen && openParen < rightParen) {
				const candidate = value.slice(leftBracket, rightParen + 1);
				if (linkRegex.test(candidate)) {
					const textOnly = candidate.match(linkRegex)?.[1] ?? candidate;
					this.value = value.slice(0, leftBracket) + textOnly + value.slice(rightParen + 1);
					textarea.value = value.slice(0, leftBracket) + textOnly + value.slice(rightParen + 1);
					const newCursor = leftBracket + textOnly.length;
					requestAnimationFrame(() => {
						textarea.setSelectionRange(newCursor, newCursor);
						this.emitChangeAndRefresh();
					});
					return;
				}
			}
			const text = selected || placeholderText;
			const md = `[${text}](${placeholderUrl})`;
			this.value = before + md + after;
			textarea.value = before + md + after;
			const cursorPosition = before.length + 1 + text.length + 2;
			requestAnimationFrame(() => {
				textarea.focus();
				textarea.setSelectionRange(cursorPosition, cursorPosition);
				this.emitChangeAndRefresh();
			});
		});
	}
	/**
	* Update active formatting states based on current selection or cursor position.
	*/
	updateActiveFormats() {
		const textarea = this.textareaEl?.shadowRoot?.querySelector("textarea");
		if (!textarea || !this.value) return;
		const start = textarea.selectionStart ?? 0;
		const end = textarea.selectionEnd ?? 0;
		this.currentSelection = {
			start,
			end
		};
		const { start: s, end: e } = this.expandSelectionToWord(start, end, this.value);
		const selectedText = this.value.slice(s, e);
		const beforeSelection = this.value.slice(0, s);
		const afterSelection = this.value.slice(e);
		const hasBoldWrap = beforeSelection.endsWith("**") && afterSelection.startsWith("**") || selectedText.startsWith("**") && selectedText.endsWith("**");
		const hasItalicWrap = beforeSelection.endsWith("*") && afterSelection.startsWith("*") && !beforeSelection.endsWith("**") || selectedText.startsWith("*") && selectedText.endsWith("*") && !selectedText.startsWith("**");
		const hasLink = /\[([^\]]+)\]\(([^)]+)\)/.test(selectedText) || this.isWithinLink(start, this.value);
		const lineStart = this.value.lastIndexOf("\n", start - 1) + 1;
		const lineEnd = this.value.indexOf("\n", start);
		const currentLine = this.value.slice(lineStart, lineEnd === -1 ? this.value.length : lineEnd);
		const headingMatch = currentLine.match(/^(#{1,6})\s/);
		const headingLevel = headingMatch ? headingMatch[1].length : 0;
		const unorderedListMatch = currentLine.match(/^\s*[-*+]\s+/);
		const orderedListMatch = currentLine.match(/^\s*\d+\.\s+/);
		const hasUnorderedList = !!unorderedListMatch;
		const hasOrderedList = !!orderedListMatch;
		const hasColorWrap = !!(beforeSelection.match(/<span style="color: ([^"]+)">/) && afterSelection.includes("</span>") || selectedText.match(/^<span style="color: ([^"]+)">(.*)<\/span>$/s));
		let activeColor = DEFAULT_PREVIEW_TEXT_COLOR;
		if (hasColorWrap) {
			const beforeColorMatch = beforeSelection.match(/<span style="color: ([^"]+)">([^<]*)$/);
			const selectedColorMatch = selectedText.match(/^<span style="color: ([^"]+)">/);
			activeColor = beforeColorMatch?.[1] || selectedColorMatch?.[1] || "";
		}
		const alignmentRegex = /<div style="text-align: (left|center|right);">/;
		let alignment = "left";
		const alignmentMatch = currentLine.match(alignmentRegex);
		if (alignmentMatch) alignment = alignmentMatch[1];
		else {
			const alignmentStartMatch = this.value.slice(0, lineStart).match(/.*<div style="text-align: (left|center|right);">[^<]*$/s);
			if (alignmentStartMatch) {
				if (this.value.slice(lineEnd).includes("</div>")) alignment = alignmentStartMatch[1];
			}
		}
		this.activeFormats = {
			bold: hasBoldWrap,
			italic: hasItalicWrap,
			link: hasLink,
			h1: headingLevel === 1,
			h2: headingLevel === 2,
			h3: headingLevel === 3,
			h4: headingLevel === 4,
			color: hasColorWrap,
			activeColor,
			unorderedList: hasUnorderedList,
			orderedList: hasOrderedList,
			alignment
		};
	}
	/**
	* Check if a given position is within a markdown link [text](url).
	* @param position - cursor position
	* @param value  - full textarea value
	*/
	isWithinLink(position, value) {
		const leftBracket = value.lastIndexOf("[", position);
		const rightParen = value.indexOf(")", position);
		if (leftBracket === -1 || rightParen === -1) return false;
		const candidate = value.slice(leftBracket, rightParen + 1);
		return /^\[([^\]]+)\]\(([^)]+)\)$/.test(candidate);
	}
	/**
	* Check if selection is within the text part [] of a markdown link
	* @param start - selection start position
	* @param end - selection end position
	* @param value - full textarea value
	*/
	isSelectionInLinkText(start, end, value) {
		const leftBracket = value.lastIndexOf("[", start);
		const rightParen = value.indexOf(")", Math.max(start, end));
		if (leftBracket === -1 || rightParen === -1) return false;
		const rightBracket = value.indexOf("]", leftBracket);
		const openParen = value.indexOf("(", rightBracket);
		if (rightBracket === -1 || openParen === -1) return false;
		if (!(leftBracket < rightBracket && rightBracket < openParen && openParen < rightParen)) return false;
		const candidate = value.slice(leftBracket, rightParen + 1);
		if (!/^\[([^\]]+)\]\(([^)]*)\)$/.test(candidate)) return false;
		return start >= leftBracket + 1 && end <= rightBracket;
	}
	/**
	* Check if selection is within the URL part () of a markdown link
	* @param start - selection start position
	* @param end - selection end position
	* @param value - full textarea value
	*/
	isSelectionInLinkUrl(start, end, value) {
		const leftBracket = value.lastIndexOf("[", start);
		const rightParen = value.indexOf(")", Math.max(start, end));
		if (leftBracket === -1 || rightParen === -1) return false;
		const rightBracket = value.indexOf("]", leftBracket);
		const openParen = value.indexOf("(", rightBracket);
		if (rightBracket === -1 || openParen === -1) return false;
		if (!(leftBracket < rightBracket && rightBracket < openParen && openParen < rightParen)) return false;
		const candidate = value.slice(leftBracket, rightParen + 1);
		if (!/^\[([^\]]+)\]\(([^)]*)\)$/.test(candidate)) return false;
		return start >= openParen + 1 && end <= rightParen;
	}
	/**
	* Add keyboard event listeners to the textarea for undo/redo functionality.
	*/
	addKeyboardListeners() {
		const tryAddListener = (attempts = 0) => {
			const textarea = this.textareaEl?.shadowRoot?.querySelector("textarea");
			if (textarea) {
				textarea.removeEventListener("keydown", this.handleKeyDown);
				textarea.addEventListener("keydown", this.handleKeyDown);
			} else if (attempts < 15) requestAnimationFrame(() => tryAddListener(attempts + 1));
		};
		tryAddListener();
	}
	/**
	* Remove keyboard event listeners from the textarea to prevent memory leaks.
	*/
	removeKeyboardListeners() {
		const textarea = this.textareaEl?.shadowRoot?.querySelector("textarea");
		if (textarea) textarea.removeEventListener("keydown", this.handleKeyDown);
	}
	/**
	* Toggle color formatting by wrapping/unwrapping selection or current word.
	*
	* @param color - the hex color code to apply, e.g. '#FF0000' for red
	*/
	toggleColorWrap(color) {
		if (this.isReadonly || this.isDisabled) return;
		this.saveUndoStateBeforeChange();
		this.withSelection((textarea, start, end, value) => {
			if (this.isSelectionInLinkUrl(start, end, value)) return;
			const { start: s, end: e } = this.expandSelectionToWord(start, end, value);
			let before = value.slice(0, s);
			let selected = value.slice(s, e);
			let after = value.slice(e);
			const colorTagClose = "</span>";
			const beforeColorMatch = before.match(/<span style="color: ([^"]+)">$/);
			if (beforeColorMatch && after.startsWith(colorTagClose)) {
				const existingColor = beforeColorMatch[1];
				let selStart;
				let selEnd;
				if (existingColor === color) {
					before = before.slice(0, before.length - beforeColorMatch[0].length);
					after = after.slice(7);
					this.value = before + selected + after;
					textarea.value = before + selected + after;
					selStart = before.length;
					selEnd = selStart + selected.length;
				} else {
					const newColorTagOpen = `<span style="color: ${color}">`;
					before = before.slice(0, before.length - beforeColorMatch[0].length) + newColorTagOpen;
					this.value = before + selected + after;
					textarea.value = before + selected + after;
					selStart = before.length;
					selEnd = selStart + selected.length;
				}
				requestAnimationFrame(() => {
					textarea.setSelectionRange(selStart, selEnd);
					this.emitChangeAndRefresh();
				});
				return;
			}
			const innerMatch = selected.match(/^<span style="color: ([^"]+)">(.*)<\/span>$/s);
			if (innerMatch) {
				const existingColor = innerMatch[1];
				const innerText = innerMatch[2];
				if (existingColor === color) selected = innerText;
				else selected = `<span style="color: ${color}">${innerText}</span>`;
				this.value = before + selected + after;
				textarea.value = before + selected + after;
				const selStart = before.length;
				const selEnd = selStart + (existingColor === color ? innerText.length : innerText.length);
				requestAnimationFrame(() => {
					textarea.setSelectionRange(selStart, selEnd);
					this.emitChangeAndRefresh();
				});
				return;
			}
			const newColorTagOpen = `<span style="color: ${color}">`;
			const wrapped = `${newColorTagOpen}${selected || ""}${colorTagClose}`;
			this.value = before + wrapped + after;
			textarea.value = before + wrapped + after;
			const selStart = before.length + newColorTagOpen.length;
			const selEnd = selStart + (selected ? selected.length : 0);
			requestAnimationFrame(() => {
				textarea.setSelectionRange(selStart, selEnd);
				this.emitChangeAndRefresh();
			});
		});
	}
	/**
	* Handle color selection from the dropdown.
	*
	* @param color - the selected hex color code
	*/
	selectColor(color) {
		if (this.savedSelection) {
			const textarea = this.textareaEl?.shadowRoot?.querySelector("textarea");
			if (textarea) {
				textarea.focus();
				textarea.setSelectionRange(this.savedSelection.start, this.savedSelection.end);
				this.currentSelection = this.savedSelection;
				this.savedSelection = null;
				this.toggleColorWrap(color);
			}
		} else this.toggleColorWrap(color);
	}
	/**
	* Clear color formatting from the current selection or word.
	*/
	clearColor() {
		if (this.isReadonly || this.isDisabled) return;
		this.saveUndoStateBeforeChange();
		if (this.savedSelection) {
			const textarea = this.textareaEl?.shadowRoot?.querySelector("textarea");
			if (textarea) {
				textarea.focus();
				textarea.setSelectionRange(this.savedSelection.start, this.savedSelection.end);
				this.currentSelection = this.savedSelection;
				this.savedSelection = null;
			}
		}
		this.withSelection((textarea, start, end, value) => {
			if (this.isSelectionInLinkUrl(start, end, value)) return;
			const { start: s, end: e } = this.expandSelectionToWord(start, end, value);
			const colorRegex = /<span style="color: ([^"]+)">(.*?)<\/span>/g;
			let match;
			let newValue = value;
			let foundSpan = false;
			colorRegex.lastIndex = 0;
			match = colorRegex.exec(value);
			while (match !== null) {
				const fullMatchStart = match.index;
				const fullMatchEnd = match.index + match[0].length;
				const spanOpenTag = `<span style="color: ${match[1]}">`;
				const contentStart = fullMatchStart + spanOpenTag.length;
				const contentEnd = fullMatchEnd - 7;
				const innerContent = match[2];
				match = colorRegex.exec(value);
				if (s >= contentStart && s < contentEnd || e > contentStart && e <= contentEnd || s <= contentStart && e >= contentEnd) {
					newValue = value.slice(0, fullMatchStart) + innerContent + value.slice(fullMatchEnd);
					foundSpan = true;
					this.value = newValue;
					textarea.value = newValue;
					let newStart = s;
					let newEnd = e;
					if (s >= fullMatchStart) {
						const spanOpenTagLength = spanOpenTag.length;
						if (s >= contentStart) newStart = s - spanOpenTagLength;
						else if (s >= fullMatchStart) newStart = fullMatchStart;
					}
					if (e >= fullMatchStart) {
						const spanOpenTagLength = spanOpenTag.length;
						if (e <= contentEnd) newEnd = e - spanOpenTagLength;
						else newEnd = e - spanOpenTagLength - 7;
					}
					requestAnimationFrame(() => {
						textarea.setSelectionRange(Math.max(0, newStart), Math.max(0, newEnd));
						this.emitChangeAndRefresh();
					});
					return;
				}
			}
			if (!foundSpan) requestAnimationFrame(() => {
				this.updateActiveFormats();
			});
		});
	}
	/**
	* Determine the current active heading level (1-3) based on activeFormats.
	*/
	getActiveHeadingLevel() {
		if (this.activeFormats.h1) return 1;
		if (this.activeFormats.h2) return 2;
		if (this.activeFormats.h3) return 3;
		if (this.activeFormats.h4) return 4;
		return 0;
	}
	/**
	* Save the initial state to the undo stack.
	*/
	saveInitialUndoState() {
		const textarea = this.textareaEl?.shadowRoot?.querySelector("textarea");
		const initialSelection = textarea ? {
			start: textarea.selectionStart ?? 0,
			end: textarea.selectionEnd ?? 0
		} : {
			start: 0,
			end: 0
		};
		this.undoStack = [{
			value: this.value,
			selection: initialSelection
		}];
		this.redoStack = [];
		this.lastSavedValue = this.value;
	}
	/**
	* Save the current state to the undo stack before making a change.
	*/
	saveUndoStateBeforeChange() {
		if (this.isUndoRedoAction) return;
		const textarea = this.textareaEl?.shadowRoot?.querySelector("textarea");
		const selection = textarea ? {
			start: textarea.selectionStart ?? 0,
			end: textarea.selectionEnd ?? 0
		} : {
			start: 0,
			end: 0
		};
		this.undoStack.push({
			value: this.value,
			selection
		});
		if (this.undoStack.length > this.MAX_UNDO_STACK_SIZE) this.undoStack.shift();
		this.redoStack = [];
	}
	/**
	* Save the current state to the undo stack if the value has changed.
	*/
	saveUndoState() {
		if (this.isUndoRedoAction) return;
		if (this.value !== this.lastSavedValue) {
			this.undoStack.push({
				value: this.lastSavedValue,
				selection: this.currentSelection
			});
			if (this.undoStack.length > this.MAX_UNDO_STACK_SIZE) this.undoStack.shift();
			this.redoStack = [];
			this.lastSavedValue = this.value;
		}
	}
	/**
	* Schedule an undo state save after a short delay.
	*/
	scheduleUndoStateSave() {
		if (this.undoTimeout) clearTimeout(this.undoTimeout);
		this.undoTimeout = window.setTimeout(() => {
			this.saveUndoState();
			this.undoTimeout = null;
		}, this.UNDO_SAVE_DELAY);
	}
	/**
	* Handle list continuation when Enter is pressed within a list item.
	* Works against the textarea value/selection.
	* Returns true if handled.
	*/
	handleListContinuation() {
		const textarea = this.textareaEl?.shadowRoot?.querySelector("textarea");
		if (!textarea) return false;
		const start = textarea.selectionStart ?? 0;
		if (start !== (textarea.selectionEnd ?? 0)) return false;
		const value = this.value;
		const lineStart = value.lastIndexOf("\n", start - 1) + 1;
		let lineEnd = value.indexOf("\n", start);
		if (lineEnd === -1) lineEnd = value.length;
		const currentLine = value.slice(lineStart, lineEnd);
		const unorderedMatch = currentLine.match(/^(\s*)([-*+])\s+(.*)$/);
		const orderedMatch = currentLine.match(/^(\s*)(\d+)\.\s+(.*)$/);
		if (!unorderedMatch && !orderedMatch) return false;
		this.saveUndoStateBeforeChange();
		if (unorderedMatch) {
			const indent = unorderedMatch[1] ?? "";
			const marker = unorderedMatch[2] ?? "-";
			if ((unorderedMatch[3] ?? "").trim() === "") {
				const before = value.slice(0, lineStart);
				const after = value.slice(lineEnd);
				const newValue = before + (lineEnd === value.length ? "" : "") + after;
				this.value = newValue;
				textarea.value = newValue;
				const newCursor = before.length;
				requestAnimationFrame(() => {
					textarea.setSelectionRange(newCursor, newCursor);
					this.emitChangeAndRefresh();
				});
				return true;
			}
			const before = value.slice(0, start);
			const after = value.slice(start);
			const prefix = `\n${indent}${marker} `;
			const newValue = before + prefix + after;
			this.value = newValue;
			textarea.value = newValue;
			const newCursor = start + prefix.length;
			textarea.setSelectionRange(newCursor, newCursor);
			this.emitChangeAndRefresh();
			return true;
		}
		if (orderedMatch) {
			const indent = orderedMatch[1] ?? "";
			const numberStr = orderedMatch[2] ?? "1";
			if ((orderedMatch[3] ?? "").trim() === "") {
				const before = value.slice(0, lineStart);
				const after = value.slice(lineEnd);
				const newValue = before + (lineEnd === value.length ? "" : "") + after;
				this.value = newValue;
				textarea.value = newValue;
				const newCursor = before.length;
				requestAnimationFrame(() => {
					textarea.setSelectionRange(newCursor, newCursor);
					this.emitChangeAndRefresh();
				});
				return true;
			}
			const nextNumber = parseInt(numberStr, 10) + 1;
			const before = value.slice(0, start);
			const after = value.slice(start);
			const prefix = `\n${indent}${nextNumber}. `;
			let newValue = before + prefix + after;
			const lines = newValue.split("\n");
			const renumberStartIndex = before.split("\n").length + 1;
			let currentNum = nextNumber + 1;
			const orderedRegex = /^(\s*)(\d+)\.\s+(.*)$/;
			for (let i = renumberStartIndex; i < lines.length; i++) {
				const match = lines[i].match(orderedRegex);
				if (match && match[1] === indent) {
					const content = match[3];
					lines[i] = `${indent}${currentNum}. ${content}`;
					currentNum++;
				} else if (match && match[1].length < indent.length) break;
			}
			newValue = lines.join("\n");
			this.value = newValue;
			textarea.value = newValue;
			const newCursor = start + prefix.length;
			textarea.setSelectionRange(newCursor, newCursor);
			this.emitChangeAndRefresh();
			return true;
		}
		return false;
	}
	/**
	* Handle Tab key to indent a list item (increase nesting level).
	* Returns true if handled.
	*/
	handleListIndent() {
		const textarea = this.textareaEl?.shadowRoot?.querySelector("textarea");
		if (!textarea) return false;
		const start = textarea.selectionStart ?? 0;
		if (start !== (textarea.selectionEnd ?? 0)) return false;
		const value = this.value;
		const lineStart = value.lastIndexOf("\n", start - 1) + 1;
		let lineEnd = value.indexOf("\n", start);
		if (lineEnd === -1) lineEnd = value.length;
		const currentLine = value.slice(lineStart, lineEnd);
		const unorderedMatch = currentLine.match(/^(\s*)([-*+])\s+(.*)$/);
		const orderedMatch = currentLine.match(/^(\s*)(\d+)\.\s+(.*)$/);
		if (!unorderedMatch && !orderedMatch) return false;
		this.saveUndoStateBeforeChange();
		const before = value.slice(0, lineStart);
		const after = value.slice(lineEnd);
		const indent = "    ";
		let newLine;
		let newCursorOffset = 0;
		if (unorderedMatch) {
			newLine = `${unorderedMatch[1] ?? ""}${indent}${unorderedMatch[2] ?? "-"} ${unorderedMatch[3] ?? ""}`;
			newCursorOffset = 4;
		} else if (orderedMatch) {
			const currentIndent = orderedMatch[1] ?? "";
			const content = orderedMatch[3] ?? "";
			if (content.trim() === "") {
				newLine = currentLine;
				const nestedLine = `${currentIndent}${indent}1. `;
				let newValue = before + newLine + "\n" + nestedLine + after;
				const parentIndent = currentIndent;
				const lines = newValue.split("\n");
				const currentLineIndex = Math.floor(lineStart / (newValue.indexOf("\n") + 1)) || newValue.slice(0, lineStart).split("\n").length - 1;
				const currentLineMatch = lines[currentLineIndex]?.match(/^(\s*)(\d+)\.\s*(.*)$/);
				const nextExpectedNumber = currentLineMatch ? parseInt(currentLineMatch[2], 10) : 1;
				const orderedRegex = /^(\s*)(\d+)\.\s*(.*)$/;
				let currentNumber = nextExpectedNumber;
				for (let i = currentLineIndex + 2; i < lines.length; i++) {
					const orderedMatch = lines[i].match(orderedRegex);
					if (orderedMatch && orderedMatch[1] === parentIndent) {
						const content = orderedMatch[3];
						lines[i] = `${parentIndent}${currentNumber}. ${content}`;
						currentNumber++;
					} else if (orderedMatch && orderedMatch[1].length < parentIndent.length) break;
				}
				newValue = lines.join("\n");
				const newCursor = lineStart + newLine.length + 1 + nestedLine.length;
				this.value = newValue;
				textarea.value = newValue;
				requestAnimationFrame(() => {
					textarea.setSelectionRange(newCursor, newCursor);
					this.emitChangeAndRefresh();
				});
				return true;
			} else {
				const newIndent = currentIndent + indent;
				let newNumber = 1;
				const lines = value.slice(0, lineStart).split("\n");
				for (let i = lines.length - 1; i >= 0; i--) {
					const match = lines[i].match(/^(\s*)(\d+)\.\s*(.*)$/);
					if (match && match[1] === newIndent) {
						newNumber = parseInt(match[2], 10) + 1;
						break;
					} else if (match && match[1].length < newIndent.length) break;
				}
				newLine = `${newIndent}${newNumber}. ${content}`;
				newCursorOffset = 4;
			}
		} else return false;
		let newValue = before + newLine + after;
		if (orderedMatch) {
			const newIndent = (orderedMatch[1] ?? "") + indent;
			newValue = this.renumberOrderedListItems(newValue, lineStart + newLine.length, newIndent);
			const parentIndent = orderedMatch[1] ?? "";
			newValue = this.renumberOrderedListItems(newValue, lineStart, parentIndent);
		}
		this.value = newValue;
		textarea.value = newValue;
		const newCursor = lineStart + (start - lineStart) + newCursorOffset;
		textarea.setSelectionRange(newCursor, newCursor);
		this.emitChangeAndRefresh();
		return true;
	}
	/**
	* Handle Shift+Tab key to outdent a list item (decrease nesting level).
	* Returns true if handled.
	*/
	handleListOutdent() {
		const textarea = this.textareaEl?.shadowRoot?.querySelector("textarea");
		if (!textarea) return false;
		const start = textarea.selectionStart ?? 0;
		if (start !== (textarea.selectionEnd ?? 0)) return false;
		const value = this.value;
		const lineStart = value.lastIndexOf("\n", start - 1) + 1;
		let lineEnd = value.indexOf("\n", start);
		if (lineEnd === -1) lineEnd = value.length;
		const currentLine = value.slice(lineStart, lineEnd);
		const unorderedMatch = currentLine.match(/^(\s*)([-*+])\s+(.*)$/);
		const orderedMatch = currentLine.match(/^(\s*)(\d+)\.\s+(.*)$/);
		if (!unorderedMatch && !orderedMatch) return false;
		const currentIndent = (unorderedMatch || orderedMatch)?.[1] ?? "";
		if (currentIndent.length < 4) return false;
		this.saveUndoStateBeforeChange();
		const before = value.slice(0, lineStart);
		const after = value.slice(lineEnd);
		let newLine;
		let newCursorOffset = 0;
		if (unorderedMatch) {
			const marker = unorderedMatch[2] ?? "-";
			const content = unorderedMatch[3] ?? "";
			newLine = `${currentIndent.slice(4)}${marker} ${content}`;
			newCursorOffset = -4;
		} else if (orderedMatch) {
			const content = orderedMatch[3] ?? "";
			const newIndent = currentIndent.slice(4);
			let newNumber = 1;
			const beforeLines = before.split("\n");
			for (let i = beforeLines.length - 1; i >= 0; i--) {
				const line = beforeLines[i];
				const match = line.match(/^(\s*)(\d+)\.\s+(.*)$/);
				if (match && match[1] === newIndent) {
					newNumber = parseInt(match[2], 10) + 1;
					break;
				} else if (line.trim() !== "" && !line.match(/^\s*[-*+]\s+/) && !match) break;
			}
			newLine = `${newIndent}${newNumber}. ${content}`;
			newCursorOffset = -4;
		} else return false;
		let newValue = before + newLine + after;
		if (orderedMatch) {
			const newIndent = currentIndent.slice(4);
			newValue = this.renumberOrderedListItems(newValue, lineStart + newLine.length, newIndent);
		}
		this.value = newValue;
		textarea.value = newValue;
		const cursorInLine = start - lineStart;
		const newCursor = Math.max(lineStart, lineStart + cursorInLine + newCursorOffset);
		requestAnimationFrame(() => {
			textarea.setSelectionRange(newCursor, newCursor);
			this.emitChangeAndRefresh();
		});
		return true;
	}
	/**
	* Renumber ordered list items that come after the given position with the same indentation level.
	*
	* @param value - The text content to process
	* @param fromPosition - Start looking for list items from this position
	* @param targetIndent - Only renumber items with this exact indentation
	* @returns The text with renumbered list items
	*/
	renumberOrderedListItems(value, fromPosition, targetIndent) {
		const lines = value.split("\n");
		const fromLineIndex = Math.max(0, value.slice(0, fromPosition).split("\n").length - 1);
		let nextExpectedNumber = 1;
		const currentMatch = lines[fromLineIndex]?.match(/^(\s*)(\d+)\.\s+(.*)$/);
		if (currentMatch && currentMatch[1] === targetIndent) nextExpectedNumber = parseInt(currentMatch[2], 10) + 1;
		else for (let i = fromLineIndex - 1; i >= 0; i--) {
			const orderedMatch = lines[i].match(/^(\s*)(\d+)\.\s+(.*)$/);
			if (orderedMatch && orderedMatch[1] === targetIndent) {
				nextExpectedNumber = parseInt(orderedMatch[2], 10) + 1;
				break;
			}
		}
		let currentNumber = nextExpectedNumber;
		const orderedRegex = /^(\s*)(\d+)\.\s+(.*)$/;
		for (let i = fromLineIndex + 1; i < lines.length; i++) {
			const line = lines[i];
			const orderedMatch = line.match(orderedRegex);
			if (orderedMatch && orderedMatch[1] === targetIndent) {
				const indent = orderedMatch[1];
				const content = orderedMatch[3];
				lines[i] = `${indent}${currentNumber}. ${content}`;
				currentNumber++;
			} else if (orderedMatch && orderedMatch[1].length < targetIndent.length) break;
			else if (line.trim() !== "" && !orderedMatch && !line.match(/^\s*[-*+]\s+/)) {
				if (targetIndent === "") break;
			}
		}
		return lines.join("\n");
	}
	/**
	* Renumber any ordered lists that come after the given position, starting each new sequence from 1.
	* This is used when removing list formatting breaks the continuity of a list.
	*
	* @param value - The text content to process
	* @param fromPosition - Start looking for list items from this position
	* @returns The text with renumbered list sequences
	*/
	renumberSubsequentOrderedLists(value, fromPosition) {
		const lines = value.split("\n");
		const fromLineIndex = Math.max(0, value.slice(0, fromPosition).split("\n").length - 1);
		const orderedRegex = /^(\s*)(\d+)\.\s+(.*)$/;
		let currentSequenceNumber = 1;
		let inSequence = false;
		let currentIndent = "";
		for (let i = fromLineIndex + 1; i < lines.length; i++) {
			const line = lines[i];
			const orderedMatch = line.match(orderedRegex);
			if (orderedMatch) {
				const indent = orderedMatch[1];
				const content = orderedMatch[3];
				if (!inSequence || indent !== currentIndent) {
					currentSequenceNumber = 1;
					inSequence = true;
					currentIndent = indent;
				}
				lines[i] = `${indent}${currentSequenceNumber}. ${content}`;
				currentSequenceNumber++;
			} else if (line.trim() !== "" && !line.match(/^\s*[-*+]\s+/)) inSequence = false;
		}
		return lines.join("\n");
	}
	/**
	* Handle backspace to remove empty list items.
	* Returns true if handled.
	*/
	handleListBackspace() {
		const textarea = this.textareaEl?.shadowRoot?.querySelector("textarea");
		if (!textarea) return false;
		const start = textarea.selectionStart ?? 0;
		if (start !== (textarea.selectionEnd ?? 0)) return false;
		const value = this.value;
		const lineStart = value.lastIndexOf("\n", start - 1) + 1;
		let lineEnd = value.indexOf("\n", start);
		if (lineEnd === -1) lineEnd = value.length;
		const currentLine = value.slice(lineStart, lineEnd);
		const cursorPositionInLine = start - lineStart;
		const unorderedMatch = currentLine.match(/^(\s*)([-*+])\s+(.*)$/);
		const orderedMatch = currentLine.match(/^(\s*)(\d+)\.\s+(.*)$/);
		if (!unorderedMatch && !orderedMatch) return false;
		let markerEndPosition = 0;
		let hasContent = false;
		if (unorderedMatch) {
			const indent = unorderedMatch[1] ?? "";
			const marker = unorderedMatch[2] ?? "-";
			const content = unorderedMatch[3] ?? "";
			markerEndPosition = indent.length + marker.length + 1;
			hasContent = content.trim().length > 0;
		} else if (orderedMatch) {
			const indent = orderedMatch[1] ?? "";
			const numberStr = orderedMatch[2] ?? "1";
			const content = orderedMatch[3] ?? "";
			markerEndPosition = indent.length + numberStr.length + 2;
			hasContent = content.trim().length > 0;
		}
		if (cursorPositionInLine >= markerEndPosition && !hasContent) {
			this.saveUndoStateBeforeChange();
			const before = value.slice(0, lineStart);
			const after = value.slice(lineEnd);
			let newValue;
			if (lineEnd === value.length) newValue = before.endsWith("\n") ? before.slice(0, -1) : before;
			else newValue = before + after.slice(1);
			if (orderedMatch) {
				const indent = orderedMatch[1] ?? "";
				const lines = newValue.split("\n");
				const startLineIndex = Math.max(0, before.split("\n").length - 1);
				let nextNumber = 1;
				for (let i = startLineIndex - 1; i >= 0; i--) {
					const match = lines[i].match(/^(\s*)(\d+)\.\s*(.*)$/);
					if (match && match[1] === indent) {
						nextNumber = parseInt(match[2], 10) + 1;
						break;
					}
				}
				for (let i = startLineIndex; i < lines.length; i++) {
					const match = lines[i].match(/^(\s*)(\d+)\.\s*(.*)$/);
					if (match && match[1] === indent) {
						lines[i] = `${indent}${nextNumber}. ${match[3]}`;
						nextNumber++;
					} else if (match && match[1].length < indent.length) break;
				}
				newValue = lines.join("\n");
			}
			this.value = newValue;
			textarea.value = newValue;
			let newCursor = before.length;
			if (newCursor > 0 && before.endsWith("\n")) newCursor = before.length - 1;
			requestAnimationFrame(() => {
				textarea.setSelectionRange(newCursor, newCursor);
				this.emitChangeAndRefresh();
			});
			return true;
		}
		return false;
	}
	/**
	/**
	* Perform an undo operation, reverting to the previous state.
	*/
	undo() {
		if (this.isReadonly || this.isDisabled || this.undoStack.length <= 1) return;
		const textarea = this.textareaEl?.shadowRoot?.querySelector("textarea");
		const currentSelection = textarea ? {
			start: textarea.selectionStart ?? 0,
			end: textarea.selectionEnd ?? 0
		} : {
			start: 0,
			end: 0
		};
		this.redoStack.push({
			value: this.value,
			selection: currentSelection
		});
		const previousState = this.undoStack.pop();
		if (!previousState) return;
		this.isUndoRedoAction = true;
		this.value = previousState.value;
		this.lastSavedValue = previousState.value;
		if (textarea) textarea.value = previousState.value;
		requestAnimationFrame(() => {
			if (textarea) textarea.setSelectionRange(previousState.selection.start, previousState.selection.end);
			this.emitChangeAndRefresh();
			this.isUndoRedoAction = false;
		});
	}
	/**
	* Perform a redo operation, reapplying a previously undone state.
	*/
	redo() {
		if (this.isReadonly || this.isDisabled || this.redoStack.length === 0) return;
		const textarea = this.textareaEl?.shadowRoot?.querySelector("textarea");
		const currentSelection = textarea ? {
			start: textarea.selectionStart ?? 0,
			end: textarea.selectionEnd ?? 0
		} : {
			start: 0,
			end: 0
		};
		this.undoStack.push({
			value: this.value,
			selection: currentSelection
		});
		const nextState = this.redoStack.pop();
		if (!nextState) return;
		this.isUndoRedoAction = true;
		this.value = nextState.value;
		this.lastSavedValue = nextState.value;
		if (textarea) textarea.value = nextState.value;
		requestAnimationFrame(() => {
			if (textarea) textarea.setSelectionRange(nextState.selection.start, nextState.selection.end);
			this.emitChangeAndRefresh();
			this.isUndoRedoAction = false;
		});
	}
	connectedCallback() {
		super.connectedCallback();
		document.addEventListener("click", this.handleOutsideClick);
	}
	firstUpdated(changedProperties) {
		super.firstUpdated(changedProperties);
		requestAnimationFrame(() => {
			this.saveInitialUndoState();
			this.updateActiveFormats();
			this.addKeyboardListeners();
			if (this.isPreview && this.value.trim()) this.scheduleAccessibilityCheck();
		});
	}
	updated(changedProperties) {
		super.updated(changedProperties);
		if ((changedProperties.has("previewBackgroundColor") || changedProperties.has("previewTextColor")) && this.isPreview && this.value.trim()) this.scheduleAccessibilityCheck();
	}
	disconnectedCallback() {
		super.disconnectedCallback();
		document.removeEventListener("click", this.handleOutsideClick);
		if (this.undoTimeout) clearTimeout(this.undoTimeout);
		if (this.accessibilityCheckTimeout) clearTimeout(this.accessibilityCheckTimeout);
		this.removeKeyboardListeners();
		this.savedSelectionForPreview = null;
	}
	/**
	* Restore focus and selection to the textarea after toolbar interactions.
	*/
	restoreFocusAndSelection() {
		const ta = this.textareaEl?.shadowRoot?.querySelector("textarea");
		if (ta) {
			ta.focus();
			const sel = this.currentSelection;
			const start = typeof sel.start === "number" ? sel.start : ta.selectionStart ?? 0;
			const end = typeof sel.end === "number" ? sel.end : ta.selectionEnd ?? 0;
			ta.setSelectionRange(start, end);
		}
	}
	buttonTemplate(icon, handler, name, isActive = false) {
		return html`
      <lukso-tooltip text=${name} placement="top">
        <lukso-button
          @click=${() => {
			this.restoreFocusAndSelection();
			handler();
		}}
          aria-label=${name}
          aria-pressed=${isActive ? "true" : "false"}
          type="button"
          variant="secondary"
          size="small"
          custom-class=${this.toolbarButton({ active: isActive })}
          is-icon
        >
          <lukso-icon
            name=${icon}
            size="small"
            pack="vuesax"
            variant="linear"
          ></lukso-icon></lukso-button
      ></lukso-tooltip>
    `;
	}
	toolbarTemplate() {
		return html`
      <div class="flex items-center gap-1">
        <!-- Heading -->
        ${this.toolsState.includes("heading") ? html`<div class=${cn(this.styles().headingMenu())}>
              <lukso-tooltip text="Heading options" placement="top">
                <lukso-button
                  id=${this.headingTriggerId}
                  @click=${(e) => {
			e.stopPropagation();
			this.isColorDropdownOpen = false;
			this.isListDropdownOpen = false;
			this.isAlignmentDropdownOpen = false;
			this.isHeadingDropdownOpen = !this.isHeadingDropdownOpen;
		}}
                  aria-expanded=${this.isHeadingDropdownOpen ? "true" : "false"}
                  aria-label="Heading options"
                  variant="secondary"
                  size="small"
                  custom-class=${this.toolbarButton({ active: this.getActiveHeadingLevel() > 0 })}
                  is-icon
                >
                  <lukso-icon
                    name="smallcaps"
                    size="small"
                    pack="vuesax"
                    variant="linear"
                  ></lukso-icon>
                </lukso-button>
              </lukso-tooltip>
              <lukso-dropdown
                id="headingDropdown"
                trigger-id=""
                size="medium"
                ?is-open=${this.isHeadingDropdownOpen}
              >
                <lukso-dropdown-option
                  ?is-selected=${this.getActiveHeadingLevel() === 0}
                  @click=${(e) => {
			e.stopPropagation();
			this.restoreFocusAndSelection();
			this.applyHeading(0);
			this.isHeadingDropdownOpen = false;
		}}
                  size="medium"
                >
                  Normal text
                </lukso-dropdown-option>
                <lukso-dropdown-option
                  ?is-selected=${this.getActiveHeadingLevel() === 1}
                  @click=${(e) => {
			e.stopPropagation();
			this.restoreFocusAndSelection();
			this.applyHeading(1);
			this.isHeadingDropdownOpen = false;
		}}
                  size="medium"
                >
                  Heading 1
                </lukso-dropdown-option>
                <lukso-dropdown-option
                  ?is-selected=${this.getActiveHeadingLevel() === 2}
                  @click=${(e) => {
			e.stopPropagation();
			this.restoreFocusAndSelection();
			this.applyHeading(2);
			this.isHeadingDropdownOpen = false;
		}}
                  size="medium"
                >
                  Heading 2
                </lukso-dropdown-option>
                <lukso-dropdown-option
                  ?is-selected=${this.getActiveHeadingLevel() === 3}
                  @click=${(e) => {
			e.stopPropagation();
			this.restoreFocusAndSelection();
			this.applyHeading(3);
			this.isHeadingDropdownOpen = false;
		}}
                  size="medium"
                >
                  Heading 3
                </lukso-dropdown-option>
                <lukso-dropdown-option
                  ?is-selected=${this.getActiveHeadingLevel() === 4}
                  @click=${(e) => {
			e.stopPropagation();
			this.restoreFocusAndSelection();
			this.applyHeading(4);
			this.isHeadingDropdownOpen = false;
		}}
                  size="medium"
                >
                  Heading 4
                </lukso-dropdown-option>
              </lukso-dropdown>
            </div>` : nothing}

        <!-- Bold -->
        ${this.toolsState.includes("bold") ? this.buttonTemplate("text-bold", () => this.toggleWrap("**"), "Bold", this.activeFormats.bold) : nothing}

        <!-- Italic -->
        ${this.toolsState.includes("italic") ? this.buttonTemplate("text-italic", () => this.toggleWrap("*"), "Italic", this.activeFormats.italic) : nothing}

        <!-- List -->
        ${this.toolsState.includes("list") ? html`
              <div class=${this.styles().listMenu()}>
                <lukso-tooltip text="List options" placement="top">
                  <lukso-button
                    id=${this.listTriggerId}
                    @click=${(e) => {
			e.stopPropagation();
			this.restoreFocusAndSelection();
			this.isHeadingDropdownOpen = false;
			this.isColorDropdownOpen = false;
			this.isAlignmentDropdownOpen = false;
			this.isListDropdownOpen = !this.isListDropdownOpen;
		}}
                    aria-expanded=${this.isListDropdownOpen ? "true" : "false"}
                    aria-label="List options"
                    variant="secondary"
                    size="small"
                    custom-class=${this.toolbarButton({ active: this.activeFormats.unorderedList || this.activeFormats.orderedList })}
                    is-icon
                  >
                    <lukso-icon
                      name="task"
                      size="small"
                      pack="vuesax"
                      variant="linear"
                    ></lukso-icon>
                  </lukso-button>
                </lukso-tooltip>
                <lukso-dropdown
                  id="listDropdown"
                  trigger-id=""
                  size="medium"
                  ?is-open=${this.isListDropdownOpen}
                >
                  <lukso-dropdown-option
                    ?is-selected=${this.getActiveListType() === "none"}
                    @click=${(e) => {
			e.stopPropagation();
			this.restoreFocusAndSelection();
			this.applyList("none");
			this.isListDropdownOpen = false;
		}}
                    size="medium"
                  >
                    No list
                  </lukso-dropdown-option>
                  <lukso-dropdown-option
                    ?is-selected=${this.getActiveListType() === "unordered"}
                    @click=${(e) => {
			e.stopPropagation();
			this.restoreFocusAndSelection();
			this.applyList("unordered");
			this.isListDropdownOpen = false;
		}}
                    size="medium"
                  >
                    Unordered
                  </lukso-dropdown-option>
                  <lukso-dropdown-option
                    ?is-selected=${this.getActiveListType() === "ordered"}
                    @click=${(e) => {
			e.stopPropagation();
			this.restoreFocusAndSelection();
			this.applyList("ordered");
			this.isListDropdownOpen = false;
		}}
                    size="medium"
                  >
                    Ordered
                  </lukso-dropdown-option>
                </lukso-dropdown>
              </div>
            ` : nothing}

        <!-- Link -->
        ${this.toolsState.includes("link") ? this.buttonTemplate("link", () => this.insertLink(), "Link", this.activeFormats.link) : nothing}

        <!-- Text Alignment -->
        ${this.toolsState.includes("alignment") ? html`
              <div class=${this.styles().alignmentMenu()}>
                <lukso-tooltip text="Text alignment" placement="top">
                  <lukso-button
                    id=${this.alignmentTriggerId}
                    @click=${(e) => {
			e.stopPropagation();
			this.restoreFocusAndSelection();
			this.isHeadingDropdownOpen = false;
			this.isColorDropdownOpen = false;
			this.isListDropdownOpen = false;
			this.isAlignmentDropdownOpen = !this.isAlignmentDropdownOpen;
		}}
                    aria-expanded=${this.isAlignmentDropdownOpen ? "true" : "false"}
                    aria-label="Text alignment"
                    variant="secondary"
                    size="small"
                    custom-class=${this.toolbarButton({ active: this.activeFormats.alignment !== "left" })}
                    is-icon
                  >
                    <lukso-icon
                      name=${this.getAlignmentIcon()}
                      size="small"
                      pack="vuesax"
                      variant="linear"
                    ></lukso-icon>
                  </lukso-button>
                </lukso-tooltip>
                <lukso-dropdown
                  id="alignmentDropdown"
                  trigger-id=""
                  size="medium"
                  ?is-open=${this.isAlignmentDropdownOpen}
                >
                  <lukso-dropdown-option
                    ?is-selected=${this.activeFormats.alignment === "left"}
                    @click=${(e) => {
			e.stopPropagation();
			this.restoreFocusAndSelection();
			this.applyAlignment("left");
			this.isAlignmentDropdownOpen = false;
		}}
                    size="medium"
                    aria-label="Align left"
                  >
                    <div style="display: flex; align-items: center; gap: 8px;">
                      <lukso-icon
                        name="textalign-left"
                        size="small"
                        pack="vuesax"
                        variant="linear"
                      ></lukso-icon>
                      Left
                    </div>
                  </lukso-dropdown-option>
                  <lukso-dropdown-option
                    ?is-selected=${this.activeFormats.alignment === "center"}
                    @click=${(e) => {
			e.stopPropagation();
			this.restoreFocusAndSelection();
			this.applyAlignment("center");
			this.isAlignmentDropdownOpen = false;
		}}
                    size="medium"
                    aria-label="Align center"
                  >
                    <div style="display: flex; align-items: center; gap: 8px;">
                      <lukso-icon
                        name="textalign-center"
                        size="small"
                        pack="vuesax"
                        variant="linear"
                      ></lukso-icon>
                      Center
                    </div>
                  </lukso-dropdown-option>
                  <lukso-dropdown-option
                    ?is-selected=${this.activeFormats.alignment === "right"}
                    @click=${(e) => {
			e.stopPropagation();
			this.restoreFocusAndSelection();
			this.applyAlignment("right");
			this.isAlignmentDropdownOpen = false;
		}}
                    size="medium"
                    aria-label="Align right"
                  >
                    <div style="display: flex; align-items: center; gap: 8px;">
                      <lukso-icon
                        name="textalign-right"
                        size="small"
                        pack="vuesax"
                        variant="linear"
                      ></lukso-icon>
                      Right
                    </div>
                  </lukso-dropdown-option>
                </lukso-dropdown>
              </div>
            ` : nothing}

        <!-- Color -->
        ${this.toolsState.includes("color") ? html`
              <div class=${this.styles().colorMenu()}>
                <lukso-tooltip text="Text color" placement="top">
                  <lukso-button
                    id=${this.colorTriggerId}
                    @click=${(e) => {
			e.stopPropagation();
			this.restoreFocusAndSelection();
			this.isHeadingDropdownOpen = false;
			this.isListDropdownOpen = false;
			this.isAlignmentDropdownOpen = false;
			if (!this.isColorDropdownOpen) {
				const ta = this.textareaEl?.shadowRoot?.querySelector("textarea");
				if (ta) this.savedSelection = {
					start: ta.selectionStart ?? 0,
					end: ta.selectionEnd ?? 0
				};
			}
			this.isColorDropdownOpen = !this.isColorDropdownOpen;
		}}
                    aria-expanded=${this.isColorDropdownOpen ? "true" : "false"}
                    aria-pressed=${this.activeFormats.color ? "true" : "false"}
                    aria-label="Text color"
                    variant="secondary"
                    size="small"
                    custom-class=${this.toolbarButton({ active: this.activeFormats.color })}
                    is-icon
                  >
                    <div
                      class="size-4 rounded-full"
                      style="background-color: ${this.activeFormats.activeColor};"
                    ></div>
                  </lukso-button>
                </lukso-tooltip>
                <lukso-dropdown
                  id="colorDropdown"
                  trigger-id=""
                  size="medium"
                  max-height="300"
                  ?is-open=${this.isColorDropdownOpen}
                >
                  <div class="grid grid-cols-8 gap-2 p-2 w-[260px]">
                    <div
                      class="col-span-8 mb-2 flex items-center justify-between"
                    >
                      <span class="text-xs text-neutral-60">Text Color</span>
                      ${this.activeFormats.color ? html`<button
                            class="text-xs text-neutral-60 hover:text-neutral-20 underline"
                            @click=${(e) => {
			e.stopPropagation();
			this.clearColor();
			this.isColorDropdownOpen = false;
		}}
                            type="button"
                            aria-label="Clear color"
                          >
                            Clear
                          </button>` : nothing}
                    </div>
                    ${this.colorSamples.map((color) => html`
                        <button
                          class="w-6 h-6 rounded-4 border transition-all ${this.activeFormats.activeColor === color ? "border-neutral-20 ring-2 ring-purple-51" : "border-neutral-90 hover:border-neutral-60"}"
                          style="background-color: ${color}"
                          title=${color}
                          aria-pressed=${this.activeFormats.activeColor === color ? "true" : "false"}
                          aria-label="Color ${color}"
                          @click=${(e) => {
			e.stopPropagation();
			this.selectColor(color);
			this.isColorDropdownOpen = false;
		}}
                        ></button>
                      `)}
                  </div>
                </lukso-dropdown>
              </div>
            ` : nothing}

        <!-- Divider -->
        ${this.toolsState.length > 0 ? html`<div class=${this.styles().divider()}></div>` : nothing}
      </div>
    `;
	}
	accessibilityIndicatorTemplate() {
		if (!this.hasAccessibilityViolations || this.accessibilityViolations.length === 0 || !this.isPreview) return nothing;
		return html`
      <div
        class="accessibility-indicator has-violations absolute top-2 right-2 z-10"
        style="pointer-events: auto;"
      >
        <lukso-tooltip placement="left">
          <div slot="text">
            <div .innerHTML=${formatViolationsForTooltip(this.accessibilityViolations)}></div>
          </div>
          <div
            class="flex cursor-help"
            role="alert"
            aria-label="Accessibility violations found"
          >
            <lukso-icon
              name="warning-2"
              size="small"
              color="red-65"
              pack="vuesax"
              variant="linear"
            ></lukso-icon>
          </div>
        </lukso-tooltip>
      </div>
    `;
	}
	render() {
		const { wrapper, header, toolbar, area, editor, preview } = this.styles({ isFullWidth: this.isFullWidth });
		if (!this.previewBackgroundColor) this.previewBackgroundColor = DEFAULT_PREVIEW_BACKGROUND_COLOR;
		if (!this.previewTextColor) this.previewTextColor = DEFAULT_PREVIEW_TEXT_COLOR;
		this.toolsState = this.defaultTools;
		try {
			if (this.tools) this.toolsState = JSON.parse(this.tools);
		} catch (error) {
			console.warn("Invalid JSON for tools property in lukso-markdown-editor. Using default tools.", error);
		}
		return html`
      <div class=${wrapper()}>
        <div>
          <lukso-form-label
            for-name=${this.name}
            label=${this.label}
          ></lukso-form-label>
          <lukso-form-description
            description=${this.description}
          ></lukso-form-description>
        </div>

        <div class="flex flex-col gap-2">
          <div class=${header()}>
            <div class=${toolbar()}>${this.toolbarTemplate()}</div>
            ${this.buttonTemplate("eye", () => this.togglePreview(), "Toggle preview", this.isPreview)}
          </div>

          <div class=${area()}>
            ${!this.isPreview ? html`<div class=${editor()}>
                  <lukso-textarea
                    .value=${this.value}
                    name=${this.name ? this.name : nothing}
                    size=${this.size ? this.size : nothing}
                    rows=${this.rows ? this.rows : nothing}
                    placeholder=${this.placeholder ? this.placeholder : nothing}
                    error=${this.error ? this.error : nothing}
                    ?is-full-width=${true}
                    ?is-disabled=${this.isDisabled}
                    ?is-readonly=${this.isReadonly}
                    ?is-non-resizable=${this.isNonResizable}
                    @on-input=${this.handleTextareaInput}
                    @on-key-up=${this.handleTextareaKeyUp}
                    @on-input-click=${this.handleTextareaClick}
                  ></lukso-textarea>
                  ${this.accessibilityIndicatorTemplate()}
                </div>` : html`<div
                  class=${preview()}
                  style="background-color: ${this.previewBackgroundColor};"
                >
                  <lukso-markdown
                    value=${this.value}
                    ?strip-html-tags=${this.stripHtmlTags}
                    custom-style=${`color: ${this.previewTextColor};`}
                  ></lukso-markdown>
                  ${this.accessibilityIndicatorTemplate()}
                </div>`}
          </div>
        </div>
      </div>
    `;
	}
};
__decorate([property({ type: String })], LuksoMarkdownEditor.prototype, "value", void 0);
__decorate([property({
	type: String,
	reflect: true
})], LuksoMarkdownEditor.prototype, "name", void 0);
__decorate([property({ type: String })], LuksoMarkdownEditor.prototype, "label", void 0);
__decorate([property({ type: String })], LuksoMarkdownEditor.prototype, "description", void 0);
__decorate([property({ type: String })], LuksoMarkdownEditor.prototype, "error", void 0);
__decorate([property({
	type: Boolean,
	attribute: "is-full-width"
})], LuksoMarkdownEditor.prototype, "isFullWidth", void 0);
__decorate([property({
	type: Boolean,
	attribute: "is-readonly"
})], LuksoMarkdownEditor.prototype, "isReadonly", void 0);
__decorate([property({
	type: Boolean,
	attribute: "is-disabled"
})], LuksoMarkdownEditor.prototype, "isDisabled", void 0);
__decorate([property({
	type: Boolean,
	attribute: "is-non-resizable"
})], LuksoMarkdownEditor.prototype, "isNonResizable", void 0);
__decorate([property({ type: Boolean })], LuksoMarkdownEditor.prototype, "autofocus", void 0);
__decorate([property({
	type: String,
	reflect: true
})], LuksoMarkdownEditor.prototype, "size", void 0);
__decorate([property({
	type: Boolean,
	attribute: "is-preview",
	reflect: true
})], LuksoMarkdownEditor.prototype, "isPreview", void 0);
__decorate([property({ type: Number })], LuksoMarkdownEditor.prototype, "rows", void 0);
__decorate([property({ type: String })], LuksoMarkdownEditor.prototype, "placeholder", void 0);
__decorate([property({
	type: String,
	attribute: "preview-background-color",
	reflect: true
})], LuksoMarkdownEditor.prototype, "previewBackgroundColor", void 0);
__decorate([property({
	type: String,
	attribute: "preview-text-color",
	reflect: true
})], LuksoMarkdownEditor.prototype, "previewTextColor", void 0);
__decorate([property({
	type: Boolean,
	attribute: "strip-html-tags"
})], LuksoMarkdownEditor.prototype, "stripHtmlTags", void 0);
__decorate([property({ type: String })], LuksoMarkdownEditor.prototype, "tools", void 0);
__decorate([state()], LuksoMarkdownEditor.prototype, "toolsState", void 0);
__decorate([state()], LuksoMarkdownEditor.prototype, "savedSelectionForPreview", void 0);
__decorate([state()], LuksoMarkdownEditor.prototype, "isHeadingDropdownOpen", void 0);
__decorate([state()], LuksoMarkdownEditor.prototype, "isColorDropdownOpen", void 0);
__decorate([state()], LuksoMarkdownEditor.prototype, "isListDropdownOpen", void 0);
__decorate([state()], LuksoMarkdownEditor.prototype, "isAlignmentDropdownOpen", void 0);
__decorate([state()], LuksoMarkdownEditor.prototype, "currentSelection", void 0);
__decorate([state()], LuksoMarkdownEditor.prototype, "savedSelection", void 0);
__decorate([state()], LuksoMarkdownEditor.prototype, "activeFormats", void 0);
__decorate([state()], LuksoMarkdownEditor.prototype, "accessibilityViolations", void 0);
__decorate([state()], LuksoMarkdownEditor.prototype, "hasAccessibilityViolations", void 0);
__decorate([query("lukso-textarea")], LuksoMarkdownEditor.prototype, "textareaEl", void 0);
LuksoMarkdownEditor = __decorate([safeCustomElement("lukso-markdown-editor")], LuksoMarkdownEditor);
//#endregion
export { LuksoMarkdownEditor };
