ngx-rich-text-pro 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -955,6 +955,16 @@ class NgxRichTextEditorComponent {
955
955
  this.mergeConfig();
956
956
  this.updateStats();
957
957
  }
958
+ ngAfterViewInit() {
959
+ try {
960
+ document.execCommand('defaultParagraphSeparator', false, 'p');
961
+ }
962
+ catch { }
963
+ const initialContent = this.content || this.value;
964
+ if (initialContent) {
965
+ this.setContent(initialContent, false);
966
+ }
967
+ }
958
968
  ngOnChanges(changes) {
959
969
  if (changes['config'] ||
960
970
  changes['tools'] ||
@@ -1207,6 +1217,14 @@ class NgxRichTextEditorComponent {
1207
1217
  this.setCaretCharacterOffsetWithin(root, offset);
1208
1218
  }
1209
1219
  }
1220
+ if (this.exclusiveInlineFormats ?? this.computedConfig.exclusiveInlineFormats) {
1221
+ const hasNested = root.querySelector('u b, u strong, u i, u em, b u, b ins, b i, b em, i u, i ins, i b, i strong') !== null;
1222
+ if (hasNested) {
1223
+ const offset = this.getCaretCharacterOffsetWithin(root);
1224
+ this.sanitizeExclusiveFormatting(root);
1225
+ this.setCaretCharacterOffsetWithin(root, offset);
1226
+ }
1227
+ }
1210
1228
  const isMarkdown = this.isMarkdownMode();
1211
1229
  const html = root.innerHTML;
1212
1230
  const markdown = this.htmlToMarkdown(root);
@@ -1232,6 +1250,10 @@ class NgxRichTextEditorComponent {
1232
1250
  }
1233
1251
  onEditorFocus(event) {
1234
1252
  this.isFocused = true;
1253
+ try {
1254
+ document.execCommand('defaultParagraphSeparator', false, 'p');
1255
+ }
1256
+ catch { }
1235
1257
  this.editorFocus.emit(event);
1236
1258
  }
1237
1259
  onEditorBlur(event) {
@@ -1618,22 +1640,850 @@ class NgxRichTextEditorComponent {
1618
1640
  toggleExclusiveInlineFormat(targetCommand, otherCommands) {
1619
1641
  if (this.disabled || this.readonly || this.isSourceView)
1620
1642
  return;
1621
- this.focusEditor();
1622
- const isTargetActive = this.commandService.queryState(targetCommand);
1623
- if (isTargetActive) {
1643
+ const sel = typeof window !== 'undefined' ? window.getSelection() : null;
1644
+ const root = this.editorContentRef?.nativeElement;
1645
+ if (!sel || sel.rangeCount === 0 || !root) {
1646
+ this.focusEditor();
1624
1647
  this.commandService.execute(targetCommand);
1648
+ this.onEditorInput();
1649
+ this.updateEditorState();
1650
+ return;
1625
1651
  }
1626
- else {
1627
- for (const cmd of otherCommands) {
1628
- if (this.commandService.queryState(cmd)) {
1629
- this.commandService.execute(cmd);
1652
+ const range = sel.getRangeAt(0);
1653
+ // If selection is outside editor root, fall back to standard execution
1654
+ if (!root.contains(range.commonAncestorContainer)) {
1655
+ this.focusEditor();
1656
+ this.commandService.execute(targetCommand);
1657
+ this.onEditorInput();
1658
+ this.updateEditorState();
1659
+ return;
1660
+ }
1661
+ if (range.collapsed) {
1662
+ this.focusEditor();
1663
+ const isTargetActive = this.commandService.queryState(targetCommand);
1664
+ if (isTargetActive) {
1665
+ this.commandService.execute(targetCommand);
1666
+ }
1667
+ else {
1668
+ for (const cmd of otherCommands) {
1669
+ if (this.commandService.queryState(cmd)) {
1670
+ this.commandService.execute(cmd);
1671
+ }
1630
1672
  }
1673
+ this.commandService.execute(targetCommand);
1674
+ }
1675
+ this.onEditorInput();
1676
+ this.updateEditorState();
1677
+ return;
1678
+ }
1679
+ // Range has selected text:
1680
+ this.normalizeRangeBoundaries(range);
1681
+ const tagMap = {
1682
+ bold: 'b',
1683
+ italic: 'i',
1684
+ underline: 'u',
1685
+ };
1686
+ const formatAncestor = this.getFormattingAncestor(range, root);
1687
+ if (formatAncestor) {
1688
+ const ancestorFormat = this.getFormatType(formatAncestor);
1689
+ const isSameFormat = ancestorFormat === targetCommand;
1690
+ if (isSameFormat) {
1691
+ // Toggle off (deselect) target format from selection inside ancestor
1692
+ this.splitAndFormatInsideAncestor(formatAncestor, range, null);
1693
+ }
1694
+ else {
1695
+ // Apply target format exclusively to selection, replacing ancestor format
1696
+ const targetTag = tagMap[targetCommand] || 'b';
1697
+ this.splitAndFormatInsideAncestor(formatAncestor, range, targetTag);
1631
1698
  }
1632
- this.commandService.execute(targetCommand);
1633
1699
  }
1700
+ else {
1701
+ const isTargetActive = this.isFormatActiveOnRange(range, targetCommand, root);
1702
+ if (isTargetActive) {
1703
+ // Toggle off target format from selection
1704
+ this.removeFormatFromRange(range, targetCommand);
1705
+ }
1706
+ else {
1707
+ // Apply target format exclusively to selection, stripping any conflicting formats
1708
+ this.applyExclusiveFormatToRange(range, targetCommand);
1709
+ }
1710
+ }
1711
+ this.sanitizeExclusiveFormatting(root);
1712
+ this.cleanupEmptyFormattingElements(root);
1713
+ this.cleanupEmptyBlockElements(root);
1714
+ this.mergeAdjacentFormattingElements(root);
1634
1715
  this.onEditorInput();
1635
1716
  this.updateEditorState();
1636
1717
  }
1718
+ getFirstTextNode(node) {
1719
+ if (node.nodeType === Node.TEXT_NODE)
1720
+ return node;
1721
+ for (let i = 0; i < node.childNodes.length; i++) {
1722
+ const found = this.getFirstTextNode(node.childNodes[i]);
1723
+ if (found)
1724
+ return found;
1725
+ }
1726
+ return null;
1727
+ }
1728
+ getLastTextNode(node) {
1729
+ if (node.nodeType === Node.TEXT_NODE)
1730
+ return node;
1731
+ for (let i = node.childNodes.length - 1; i >= 0; i--) {
1732
+ const found = this.getLastTextNode(node.childNodes[i]);
1733
+ if (found)
1734
+ return found;
1735
+ }
1736
+ return null;
1737
+ }
1738
+ normalizeRangeBoundaries(range, root) {
1739
+ if (range.collapsed)
1740
+ return;
1741
+ // 1. Normalize endContainer
1742
+ if (range.endContainer.nodeType === Node.ELEMENT_NODE) {
1743
+ const el = range.endContainer;
1744
+ if (range.endOffset === 0) {
1745
+ let prev = null;
1746
+ let curr = el;
1747
+ while (curr && curr !== root) {
1748
+ if (curr.previousSibling) {
1749
+ prev = curr.previousSibling;
1750
+ break;
1751
+ }
1752
+ curr = curr.parentNode;
1753
+ }
1754
+ if (prev) {
1755
+ const lastText = this.getLastTextNode(prev);
1756
+ if (lastText) {
1757
+ range.setEnd(lastText, lastText.textContent?.length || 0);
1758
+ }
1759
+ else {
1760
+ range.setEnd(prev, prev.childNodes.length);
1761
+ }
1762
+ }
1763
+ }
1764
+ else if (el.childNodes.length > 0) {
1765
+ const child = el.childNodes[Math.min(range.endOffset - 1, el.childNodes.length - 1)];
1766
+ const lastText = this.getLastTextNode(child);
1767
+ if (lastText) {
1768
+ range.setEnd(lastText, lastText.textContent?.length || 0);
1769
+ }
1770
+ else {
1771
+ range.setEnd(child, child.childNodes.length);
1772
+ }
1773
+ }
1774
+ }
1775
+ else if (range.endContainer.nodeType === Node.TEXT_NODE && range.endOffset === 0) {
1776
+ let prev = null;
1777
+ let curr = range.endContainer;
1778
+ while (curr && curr !== root) {
1779
+ if (curr.previousSibling) {
1780
+ prev = curr.previousSibling;
1781
+ break;
1782
+ }
1783
+ curr = curr.parentNode;
1784
+ }
1785
+ if (prev) {
1786
+ const lastText = this.getLastTextNode(prev);
1787
+ if (lastText) {
1788
+ range.setEnd(lastText, lastText.textContent?.length || 0);
1789
+ }
1790
+ else {
1791
+ range.setEnd(prev, prev.childNodes.length);
1792
+ }
1793
+ }
1794
+ }
1795
+ // 2. Normalize startContainer
1796
+ if (range.startContainer.nodeType === Node.ELEMENT_NODE) {
1797
+ const el = range.startContainer;
1798
+ if (el.childNodes.length > 0 && range.startOffset < el.childNodes.length) {
1799
+ const child = el.childNodes[range.startOffset];
1800
+ const firstText = this.getFirstTextNode(child);
1801
+ if (firstText) {
1802
+ range.setStart(firstText, 0);
1803
+ }
1804
+ }
1805
+ else if (range.startOffset >= el.childNodes.length) {
1806
+ let next = null;
1807
+ let curr = el;
1808
+ while (curr && curr !== root) {
1809
+ if (curr.nextSibling) {
1810
+ next = curr.nextSibling;
1811
+ break;
1812
+ }
1813
+ curr = curr.parentNode;
1814
+ }
1815
+ if (next) {
1816
+ const firstText = this.getFirstTextNode(next);
1817
+ if (firstText) {
1818
+ range.setStart(firstText, 0);
1819
+ }
1820
+ else {
1821
+ range.setStart(next, 0);
1822
+ }
1823
+ }
1824
+ }
1825
+ }
1826
+ else if (range.startContainer.nodeType === Node.TEXT_NODE &&
1827
+ range.startOffset >= (range.startContainer.textContent?.length || 0)) {
1828
+ let next = null;
1829
+ let curr = range.startContainer;
1830
+ while (curr && curr !== root) {
1831
+ if (curr.nextSibling) {
1832
+ next = curr.nextSibling;
1833
+ break;
1834
+ }
1835
+ curr = curr.parentNode;
1836
+ }
1837
+ if (next) {
1838
+ const firstText = this.getFirstTextNode(next);
1839
+ if (firstText) {
1840
+ range.setStart(firstText, 0);
1841
+ }
1842
+ else {
1843
+ range.setStart(next, 0);
1844
+ }
1845
+ }
1846
+ }
1847
+ }
1848
+ getFormattingAncestor(range, root) {
1849
+ const isFormatTag = (el) => ['b', 'strong', 'i', 'em', 'u', 'ins'].includes(el.tagName.toLowerCase());
1850
+ let node = range.commonAncestorContainer;
1851
+ while (node && node !== root) {
1852
+ if (node.nodeType === Node.ELEMENT_NODE && isFormatTag(node)) {
1853
+ return node;
1854
+ }
1855
+ node = node.parentNode;
1856
+ }
1857
+ let startAnc = null;
1858
+ let sNode = range.startContainer;
1859
+ while (sNode && sNode !== root) {
1860
+ if (sNode.nodeType === Node.ELEMENT_NODE && isFormatTag(sNode)) {
1861
+ startAnc = sNode;
1862
+ break;
1863
+ }
1864
+ sNode = sNode.parentNode;
1865
+ }
1866
+ let endAnc = null;
1867
+ let eNode = range.endContainer;
1868
+ while (eNode && eNode !== root) {
1869
+ if (eNode.nodeType === Node.ELEMENT_NODE && isFormatTag(eNode)) {
1870
+ endAnc = eNode;
1871
+ break;
1872
+ }
1873
+ eNode = eNode.parentNode;
1874
+ }
1875
+ if (startAnc && endAnc && startAnc === endAnc) {
1876
+ return startAnc;
1877
+ }
1878
+ return null;
1879
+ }
1880
+ getFormatType(el) {
1881
+ const tag = el.tagName.toLowerCase();
1882
+ if (tag === 'b' || tag === 'strong')
1883
+ return 'bold';
1884
+ if (tag === 'i' || tag === 'em')
1885
+ return 'italic';
1886
+ if (tag === 'u' || tag === 'ins')
1887
+ return 'underline';
1888
+ return null;
1889
+ }
1890
+ splitAndFormatInsideAncestor(ancestor, range, newTag) {
1891
+ const parentNode = ancestor.parentNode;
1892
+ if (!parentNode)
1893
+ return;
1894
+ this.normalizeRangeBoundaries(range);
1895
+ if (range.startContainer === range.endContainer &&
1896
+ range.startContainer.nodeType === Node.TEXT_NODE) {
1897
+ const textNode = range.startContainer;
1898
+ const startOffset = range.startOffset;
1899
+ const endOffset = range.endOffset;
1900
+ // 1. Split after selection
1901
+ const afterText = textNode.splitText(endOffset);
1902
+ // 2. Split before selection
1903
+ const selectedText = startOffset > 0 ? textNode.splitText(startOffset) : textNode;
1904
+ // Collect all siblings after selectedText inside ancestor
1905
+ const afterNodes = [];
1906
+ let sibling = selectedText.nextSibling;
1907
+ while (sibling) {
1908
+ afterNodes.push(sibling);
1909
+ sibling = sibling.nextSibling;
1910
+ }
1911
+ // Move afterNodes into a clone of ancestor
1912
+ const cloneAfter = ancestor.cloneNode(false);
1913
+ for (const node of afterNodes) {
1914
+ cloneAfter.appendChild(node);
1915
+ }
1916
+ let middleNode;
1917
+ if (newTag) {
1918
+ const wrapper = document.createElement(newTag);
1919
+ wrapper.appendChild(selectedText);
1920
+ middleNode = wrapper;
1921
+ }
1922
+ else {
1923
+ middleNode = selectedText;
1924
+ }
1925
+ parentNode.insertBefore(middleNode, ancestor.nextSibling);
1926
+ if (cloneAfter.hasChildNodes() &&
1927
+ (cloneAfter.textContent !== '' || cloneAfter.querySelector('img, br'))) {
1928
+ parentNode.insertBefore(cloneAfter, middleNode.nextSibling);
1929
+ }
1930
+ if (!ancestor.hasChildNodes() ||
1931
+ (ancestor.textContent === '' && !ancestor.querySelector('img, br'))) {
1932
+ ancestor.remove();
1933
+ }
1934
+ const sel = typeof window !== 'undefined' ? window.getSelection() : null;
1935
+ if (sel) {
1936
+ sel.removeAllRanges();
1937
+ const newRange = document.createRange();
1938
+ if (middleNode.nodeType === Node.ELEMENT_NODE && middleNode.firstChild) {
1939
+ newRange.setStart(middleNode.firstChild, 0);
1940
+ newRange.setEnd(middleNode.lastChild, middleNode.lastChild.textContent?.length || 0);
1941
+ }
1942
+ else if (middleNode.nodeType === Node.TEXT_NODE) {
1943
+ newRange.setStart(middleNode, 0);
1944
+ newRange.setEnd(middleNode, middleNode.textContent?.length || 0);
1945
+ }
1946
+ else {
1947
+ newRange.selectNodeContents(middleNode);
1948
+ }
1949
+ sel.addRange(newRange);
1950
+ }
1951
+ return;
1952
+ }
1953
+ // General multi-node fallback within ancestor:
1954
+ const fragment = range.extractContents();
1955
+ const formatEls = Array.from(fragment.querySelectorAll('b, strong, i, em, u, ins'));
1956
+ for (const el of formatEls) {
1957
+ while (el.firstChild) {
1958
+ el.parentNode?.insertBefore(el.firstChild, el);
1959
+ }
1960
+ el.remove();
1961
+ }
1962
+ const styledEls = Array.from(fragment.querySelectorAll('*'));
1963
+ for (const el of styledEls) {
1964
+ if (el.style) {
1965
+ el.style.fontWeight = '';
1966
+ el.style.fontStyle = '';
1967
+ el.style.textDecoration = '';
1968
+ }
1969
+ }
1970
+ const cloneAfter = ancestor.cloneNode(false);
1971
+ const tailRange = document.createRange();
1972
+ tailRange.selectNodeContents(ancestor);
1973
+ tailRange.setStart(range.startContainer, range.startOffset);
1974
+ const tailFragment = tailRange.extractContents();
1975
+ cloneAfter.appendChild(tailFragment);
1976
+ const insertionRef = ancestor.nextSibling;
1977
+ let selectedNodes = [];
1978
+ if (newTag) {
1979
+ const wrapper = document.createElement(newTag);
1980
+ wrapper.appendChild(fragment);
1981
+ parentNode.insertBefore(wrapper, insertionRef);
1982
+ selectedNodes = [wrapper];
1983
+ }
1984
+ else {
1985
+ const nodes = Array.from(fragment.childNodes);
1986
+ for (const node of nodes) {
1987
+ parentNode.insertBefore(node, insertionRef);
1988
+ }
1989
+ selectedNodes = nodes;
1990
+ }
1991
+ if (cloneAfter.hasChildNodes() &&
1992
+ (cloneAfter.textContent !== '' || cloneAfter.querySelector('img, br'))) {
1993
+ parentNode.insertBefore(cloneAfter, insertionRef);
1994
+ }
1995
+ if (!ancestor.hasChildNodes() ||
1996
+ (ancestor.textContent === '' && !ancestor.querySelector('img, br'))) {
1997
+ ancestor.remove();
1998
+ }
1999
+ const sel = typeof window !== 'undefined' ? window.getSelection() : null;
2000
+ if (sel && selectedNodes.length > 0) {
2001
+ sel.removeAllRanges();
2002
+ const newRange = document.createRange();
2003
+ const first = selectedNodes[0];
2004
+ const last = selectedNodes[selectedNodes.length - 1];
2005
+ if (first.nodeType === Node.ELEMENT_NODE && first.firstChild) {
2006
+ newRange.setStart(first.firstChild, 0);
2007
+ }
2008
+ else if (first.nodeType === Node.TEXT_NODE) {
2009
+ newRange.setStart(first, 0);
2010
+ }
2011
+ else {
2012
+ newRange.setStartBefore(first);
2013
+ }
2014
+ if (last.nodeType === Node.ELEMENT_NODE && last.lastChild) {
2015
+ newRange.setEnd(last.lastChild, last.lastChild.textContent?.length || 0);
2016
+ }
2017
+ else if (last.nodeType === Node.TEXT_NODE) {
2018
+ newRange.setEnd(last, last.textContent?.length || 0);
2019
+ }
2020
+ else {
2021
+ newRange.setEndAfter(last);
2022
+ }
2023
+ sel.addRange(newRange);
2024
+ }
2025
+ }
2026
+ mergeAdjacentFormattingElements(container) {
2027
+ const formatTags = ['b', 'strong', 'i', 'em', 'u', 'ins'];
2028
+ for (const tag of formatTags) {
2029
+ const els = Array.from(container.querySelectorAll(tag));
2030
+ for (const el of els) {
2031
+ if (!el.parentNode)
2032
+ continue;
2033
+ let next = el.nextSibling;
2034
+ while (next &&
2035
+ next.nodeType === Node.ELEMENT_NODE &&
2036
+ next.tagName.toLowerCase() === tag) {
2037
+ const nextEl = next;
2038
+ while (nextEl.firstChild) {
2039
+ el.appendChild(nextEl.firstChild);
2040
+ }
2041
+ const toRemove = nextEl;
2042
+ next = nextEl.nextSibling;
2043
+ toRemove.remove();
2044
+ }
2045
+ }
2046
+ }
2047
+ }
2048
+ getTextNodesInRange(range, root) {
2049
+ const textNodes = [];
2050
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null);
2051
+ let node = walker.nextNode();
2052
+ while (node) {
2053
+ const textNode = node;
2054
+ try {
2055
+ if (range.intersectsNode(textNode)) {
2056
+ let isIncluded = true;
2057
+ if (range.startContainer === textNode &&
2058
+ range.startOffset >= (textNode.textContent?.length || 0)) {
2059
+ isIncluded = false;
2060
+ }
2061
+ if (range.endContainer === textNode && range.endOffset === 0) {
2062
+ isIncluded = false;
2063
+ }
2064
+ if (isIncluded) {
2065
+ textNodes.push(textNode);
2066
+ }
2067
+ }
2068
+ }
2069
+ catch {
2070
+ // ignore detached
2071
+ }
2072
+ node = walker.nextNode();
2073
+ }
2074
+ return textNodes;
2075
+ }
2076
+ getLeafBlocksInRange(range, root) {
2077
+ const blockSelector = 'p, div, li, h1, h2, h3, h4, h5, h6, blockquote, pre';
2078
+ const allBlocks = Array.from(root.querySelectorAll(blockSelector));
2079
+ const leafBlocks = allBlocks.filter((block) => {
2080
+ return !block.querySelector(blockSelector);
2081
+ });
2082
+ if (leafBlocks.length === 0) {
2083
+ return [];
2084
+ }
2085
+ return leafBlocks.filter((block) => {
2086
+ try {
2087
+ return range.intersectsNode(block);
2088
+ }
2089
+ catch {
2090
+ return false;
2091
+ }
2092
+ });
2093
+ }
2094
+ isFormatActiveOnRange(range, targetCommand, root) {
2095
+ const formatAncestor = this.getFormattingAncestor(range, root);
2096
+ if (formatAncestor) {
2097
+ return this.getFormatType(formatAncestor) === targetCommand;
2098
+ }
2099
+ const textNodes = this.getTextNodesInRange(range, root);
2100
+ if (textNodes.length > 0) {
2101
+ const isTargetTag = (el) => {
2102
+ const tag = el.tagName.toLowerCase();
2103
+ if (targetCommand === 'bold')
2104
+ return tag === 'b' || tag === 'strong';
2105
+ if (targetCommand === 'italic')
2106
+ return tag === 'i' || tag === 'em';
2107
+ if (targetCommand === 'underline')
2108
+ return tag === 'u' || tag === 'ins';
2109
+ return false;
2110
+ };
2111
+ const nonEmptyTextNodes = textNodes.filter((t) => (t.textContent || '').trim().length > 0);
2112
+ if (nonEmptyTextNodes.length > 0) {
2113
+ const allFormatted = nonEmptyTextNodes.every((node) => {
2114
+ let cur = node.parentNode;
2115
+ while (cur && cur !== root) {
2116
+ if (cur.nodeType === Node.ELEMENT_NODE && isTargetTag(cur)) {
2117
+ return true;
2118
+ }
2119
+ cur = cur.parentNode;
2120
+ }
2121
+ return false;
2122
+ });
2123
+ if (allFormatted) {
2124
+ return true;
2125
+ }
2126
+ }
2127
+ }
2128
+ return this.commandService.queryState(targetCommand);
2129
+ }
2130
+ applyExclusiveFormatToRange(range, targetCommand) {
2131
+ const root = this.editorContentRef?.nativeElement;
2132
+ if (!root)
2133
+ return;
2134
+ const leafBlocks = this.getLeafBlocksInRange(range, root);
2135
+ if (leafBlocks.length > 1) {
2136
+ let firstWrapper = null;
2137
+ let lastWrapper = null;
2138
+ for (const block of leafBlocks) {
2139
+ const text = block.textContent || '';
2140
+ if (text.trim() === '') {
2141
+ continue;
2142
+ }
2143
+ const subRange = document.createRange();
2144
+ if (block.contains(range.startContainer)) {
2145
+ subRange.setStart(range.startContainer, range.startOffset);
2146
+ }
2147
+ else {
2148
+ const firstText = this.getFirstTextNode(block) || block;
2149
+ subRange.setStart(firstText, 0);
2150
+ }
2151
+ if (block.contains(range.endContainer)) {
2152
+ subRange.setEnd(range.endContainer, range.endOffset);
2153
+ }
2154
+ else {
2155
+ const lastText = this.getLastTextNode(block) || block;
2156
+ const len = lastText.nodeType === Node.TEXT_NODE
2157
+ ? lastText.textContent?.length || 0
2158
+ : lastText.childNodes.length;
2159
+ subRange.setEnd(lastText, len);
2160
+ }
2161
+ if (!subRange.collapsed) {
2162
+ const wrapper = this.applyExclusiveFormatToSingleRange(subRange, targetCommand);
2163
+ if (wrapper) {
2164
+ if (!firstWrapper)
2165
+ firstWrapper = wrapper;
2166
+ lastWrapper = wrapper;
2167
+ }
2168
+ }
2169
+ }
2170
+ const sel = typeof window !== 'undefined' ? window.getSelection() : null;
2171
+ if (sel && firstWrapper && lastWrapper) {
2172
+ sel.removeAllRanges();
2173
+ const newRange = document.createRange();
2174
+ newRange.setStartBefore(firstWrapper);
2175
+ newRange.setEndAfter(lastWrapper);
2176
+ sel.addRange(newRange);
2177
+ }
2178
+ }
2179
+ else {
2180
+ const wrapper = this.applyExclusiveFormatToSingleRange(range, targetCommand);
2181
+ const sel = typeof window !== 'undefined' ? window.getSelection() : null;
2182
+ if (sel && wrapper) {
2183
+ sel.removeAllRanges();
2184
+ const newRange = document.createRange();
2185
+ newRange.selectNodeContents(wrapper);
2186
+ sel.addRange(newRange);
2187
+ }
2188
+ }
2189
+ }
2190
+ applyExclusiveFormatToSingleRange(range, targetCommand) {
2191
+ const root = this.editorContentRef?.nativeElement;
2192
+ if (root) {
2193
+ this.expandRangeToEnclosingFormat(range, root);
2194
+ }
2195
+ const tagMap = {
2196
+ bold: 'b',
2197
+ italic: 'i',
2198
+ underline: 'u',
2199
+ };
2200
+ const targetTag = tagMap[targetCommand] || 'b';
2201
+ const fragment = range.extractContents();
2202
+ const formatEls = Array.from(fragment.querySelectorAll('b, strong, i, em, u, ins'));
2203
+ for (const el of formatEls) {
2204
+ while (el.firstChild) {
2205
+ el.parentNode?.insertBefore(el.firstChild, el);
2206
+ }
2207
+ el.remove();
2208
+ }
2209
+ const styledEls = Array.from(fragment.querySelectorAll('*'));
2210
+ for (const el of styledEls) {
2211
+ if (el.style) {
2212
+ el.style.fontWeight = '';
2213
+ el.style.fontStyle = '';
2214
+ el.style.textDecoration = '';
2215
+ }
2216
+ }
2217
+ if (!fragment.hasChildNodes() || fragment.textContent === '') {
2218
+ range.insertNode(fragment);
2219
+ return null;
2220
+ }
2221
+ const wrapper = document.createElement(targetTag);
2222
+ wrapper.appendChild(fragment);
2223
+ range.insertNode(wrapper);
2224
+ return wrapper;
2225
+ }
2226
+ removeFormatFromRange(range, targetCommand) {
2227
+ const root = this.editorContentRef?.nativeElement;
2228
+ if (!root)
2229
+ return;
2230
+ const leafBlocks = this.getLeafBlocksInRange(range, root);
2231
+ if (leafBlocks.length > 1) {
2232
+ let firstBlock = null;
2233
+ let lastBlock = null;
2234
+ for (const block of leafBlocks) {
2235
+ const text = block.textContent || '';
2236
+ if (text.trim() === '') {
2237
+ continue;
2238
+ }
2239
+ const subRange = document.createRange();
2240
+ if (block.contains(range.startContainer)) {
2241
+ subRange.setStart(range.startContainer, range.startOffset);
2242
+ }
2243
+ else {
2244
+ const firstText = this.getFirstTextNode(block) || block;
2245
+ subRange.setStart(firstText, 0);
2246
+ }
2247
+ if (block.contains(range.endContainer)) {
2248
+ subRange.setEnd(range.endContainer, range.endOffset);
2249
+ }
2250
+ else {
2251
+ const lastText = this.getLastTextNode(block) || block;
2252
+ const len = lastText.nodeType === Node.TEXT_NODE
2253
+ ? lastText.textContent?.length || 0
2254
+ : lastText.childNodes.length;
2255
+ subRange.setEnd(lastText, len);
2256
+ }
2257
+ if (!subRange.collapsed) {
2258
+ this.removeFormatFromSingleRange(subRange, targetCommand);
2259
+ if (!firstBlock)
2260
+ firstBlock = block;
2261
+ lastBlock = block;
2262
+ }
2263
+ }
2264
+ const sel = typeof window !== 'undefined' ? window.getSelection() : null;
2265
+ if (sel && firstBlock && lastBlock) {
2266
+ const firstText = this.getFirstTextNode(firstBlock) || firstBlock;
2267
+ const lastText = this.getLastTextNode(lastBlock) || lastBlock;
2268
+ sel.removeAllRanges();
2269
+ const newRange = document.createRange();
2270
+ newRange.setStart(firstText, 0);
2271
+ newRange.setEnd(lastText, lastText.nodeType === Node.TEXT_NODE
2272
+ ? lastText.textContent?.length || 0
2273
+ : lastText.childNodes.length);
2274
+ sel.addRange(newRange);
2275
+ }
2276
+ }
2277
+ else {
2278
+ this.removeFormatFromSingleRange(range, targetCommand);
2279
+ const sel = typeof window !== 'undefined' ? window.getSelection() : null;
2280
+ if (sel) {
2281
+ sel.removeAllRanges();
2282
+ sel.addRange(range);
2283
+ }
2284
+ }
2285
+ }
2286
+ removeFormatFromSingleRange(range, targetCommand) {
2287
+ const root = this.editorContentRef?.nativeElement;
2288
+ if (root) {
2289
+ this.expandRangeToEnclosingFormat(range, root);
2290
+ }
2291
+ const formatTags = {
2292
+ bold: ['b', 'strong'],
2293
+ italic: ['i', 'em'],
2294
+ underline: ['u', 'ins'],
2295
+ };
2296
+ const tagsToRemove = formatTags[targetCommand] || [];
2297
+ const fragment = range.extractContents();
2298
+ const selector = tagsToRemove.join(', ');
2299
+ if (selector) {
2300
+ const matchedEls = Array.from(fragment.querySelectorAll(selector));
2301
+ for (const el of matchedEls) {
2302
+ while (el.firstChild) {
2303
+ el.parentNode?.insertBefore(el.firstChild, el);
2304
+ }
2305
+ el.remove();
2306
+ }
2307
+ }
2308
+ const styledEls = Array.from(fragment.querySelectorAll('*'));
2309
+ for (const el of styledEls) {
2310
+ if (el.style) {
2311
+ if (targetCommand === 'bold')
2312
+ el.style.fontWeight = '';
2313
+ if (targetCommand === 'italic')
2314
+ el.style.fontStyle = '';
2315
+ if (targetCommand === 'underline')
2316
+ el.style.textDecoration = '';
2317
+ }
2318
+ }
2319
+ const firstChild = fragment.firstChild;
2320
+ const lastChild = fragment.lastChild;
2321
+ range.insertNode(fragment);
2322
+ if (firstChild && lastChild) {
2323
+ range.setStartBefore(firstChild);
2324
+ range.setEndAfter(lastChild);
2325
+ }
2326
+ }
2327
+ expandRangeToEnclosingFormat(range, root) {
2328
+ const isFormattingTag = (el) => {
2329
+ const tag = el.tagName.toLowerCase();
2330
+ return ['b', 'strong', 'i', 'em', 'u', 'ins'].includes(tag);
2331
+ };
2332
+ let startNode = range.startContainer;
2333
+ while (startNode && startNode !== root) {
2334
+ if (startNode.nodeType === Node.ELEMENT_NODE && isFormattingTag(startNode)) {
2335
+ if (range.startOffset === 0) {
2336
+ range.setStartBefore(startNode);
2337
+ }
2338
+ break;
2339
+ }
2340
+ if (startNode.previousSibling)
2341
+ break;
2342
+ startNode = startNode.parentNode;
2343
+ }
2344
+ let endNode = range.endContainer;
2345
+ while (endNode && endNode !== root) {
2346
+ if (endNode.nodeType === Node.ELEMENT_NODE && isFormattingTag(endNode)) {
2347
+ const len = endNode.childNodes.length;
2348
+ if (range.endOffset >= len) {
2349
+ range.setEndAfter(endNode);
2350
+ }
2351
+ break;
2352
+ }
2353
+ if (endNode.nextSibling)
2354
+ break;
2355
+ endNode = endNode.parentNode;
2356
+ }
2357
+ }
2358
+ sanitizeExclusiveFormatting(container) {
2359
+ // 1. Unwrap redundant same-tag nesting (e.g. <b><b>text</b></b> -> <b>text</b>)
2360
+ const boldEls = container.querySelectorAll('b, strong');
2361
+ boldEls.forEach((b) => {
2362
+ const innerBolds = b.querySelectorAll('b, strong');
2363
+ innerBolds.forEach((inner) => {
2364
+ while (inner.firstChild) {
2365
+ inner.parentNode?.insertBefore(inner.firstChild, inner);
2366
+ }
2367
+ inner.remove();
2368
+ });
2369
+ });
2370
+ const italicEls = container.querySelectorAll('i, em');
2371
+ italicEls.forEach((i) => {
2372
+ const innerItalics = i.querySelectorAll('i, em');
2373
+ innerItalics.forEach((inner) => {
2374
+ while (inner.firstChild) {
2375
+ inner.parentNode?.insertBefore(inner.firstChild, inner);
2376
+ }
2377
+ inner.remove();
2378
+ });
2379
+ });
2380
+ const underEls = container.querySelectorAll('u, ins');
2381
+ underEls.forEach((u) => {
2382
+ const innerUnders = u.querySelectorAll('u, ins');
2383
+ innerUnders.forEach((inner) => {
2384
+ while (inner.firstChild) {
2385
+ inner.parentNode?.insertBefore(inner.firstChild, inner);
2386
+ }
2387
+ inner.remove();
2388
+ });
2389
+ });
2390
+ // 2. Split any parent formatting tag around any different inner formatting tags
2391
+ let passes = 0;
2392
+ while (passes < 5) {
2393
+ passes++;
2394
+ let hasNested = false;
2395
+ // If <u> contains <b> or <i>: split <u> around <b>, <strong>, <i>, <em>
2396
+ const uWithOthers = Array.from(container.querySelectorAll('u, ins')).filter((el) => el.querySelector('b, strong, i, em') !== null);
2397
+ for (const parent of uWithOthers) {
2398
+ if (parent.parentNode) {
2399
+ hasNested = true;
2400
+ this.splitAroundInnerTags(parent, ['b', 'strong', 'i', 'em']);
2401
+ }
2402
+ }
2403
+ // If <b> contains <u> or <i>: split <b> around <u>, <ins>, <i>, <em>
2404
+ const bWithOthers = Array.from(container.querySelectorAll('b, strong')).filter((el) => el.querySelector('u, ins, i, em') !== null);
2405
+ for (const parent of bWithOthers) {
2406
+ if (parent.parentNode) {
2407
+ hasNested = true;
2408
+ this.splitAroundInnerTags(parent, ['u', 'ins', 'i', 'em']);
2409
+ }
2410
+ }
2411
+ // If <i> contains <u> or <b>: split <i> around <u>, <ins>, <b>, <strong>
2412
+ const iWithOthers = Array.from(container.querySelectorAll('i, em')).filter((el) => el.querySelector('u, ins, b, strong') !== null);
2413
+ for (const parent of iWithOthers) {
2414
+ if (parent.parentNode) {
2415
+ hasNested = true;
2416
+ this.splitAroundInnerTags(parent, ['u', 'ins', 'b', 'strong']);
2417
+ }
2418
+ }
2419
+ if (!hasNested)
2420
+ break;
2421
+ }
2422
+ this.cleanupEmptyFormattingElements(container);
2423
+ }
2424
+ splitAroundInnerTags(parent, innerTagNames) {
2425
+ const parentTag = parent.tagName.toLowerCase();
2426
+ const children = Array.from(parent.childNodes);
2427
+ const parentNode = parent.parentNode;
2428
+ if (!parentNode)
2429
+ return;
2430
+ let currentGroup = [];
2431
+ const flushGroup = () => {
2432
+ if (currentGroup.length === 0)
2433
+ return;
2434
+ const clone = document.createElement(parentTag);
2435
+ if (parent.style.cssText)
2436
+ clone.style.cssText = parent.style.cssText;
2437
+ if (parent.className)
2438
+ clone.className = parent.className;
2439
+ for (const node of currentGroup) {
2440
+ clone.appendChild(node);
2441
+ }
2442
+ parentNode.insertBefore(clone, parent);
2443
+ currentGroup = [];
2444
+ };
2445
+ for (const child of children) {
2446
+ if (child.nodeType === Node.ELEMENT_NODE &&
2447
+ innerTagNames.includes(child.tagName.toLowerCase())) {
2448
+ flushGroup();
2449
+ parentNode.insertBefore(child, parent);
2450
+ }
2451
+ else {
2452
+ currentGroup.push(child);
2453
+ }
2454
+ }
2455
+ flushGroup();
2456
+ parent.remove();
2457
+ }
2458
+ cleanupEmptyFormattingElements(container) {
2459
+ const els = Array.from(container.querySelectorAll('b, strong, i, em, u, ins'));
2460
+ els.forEach((el) => {
2461
+ // If the formatting element only contains a <br>, unwrap the <br>
2462
+ if (el.textContent === '' && el.querySelector('br')) {
2463
+ while (el.firstChild) {
2464
+ el.parentNode?.insertBefore(el.firstChild, el);
2465
+ }
2466
+ el.remove();
2467
+ return;
2468
+ }
2469
+ if (!el.hasChildNodes() || (el.textContent === '' && !el.querySelector('img'))) {
2470
+ el.remove();
2471
+ }
2472
+ });
2473
+ }
2474
+ cleanupEmptyBlockElements(container) {
2475
+ const blockEls = Array.from(container.querySelectorAll('p, div'));
2476
+ blockEls.forEach((el) => {
2477
+ if (el.childNodes.length === 0) {
2478
+ el.remove();
2479
+ return;
2480
+ }
2481
+ if (el.textContent === '' &&
2482
+ !el.querySelector('br, img, hr, iframe, video, audio, table, svg, canvas')) {
2483
+ el.remove();
2484
+ }
2485
+ });
2486
+ }
1637
2487
  strike() {
1638
2488
  this.exec('strikeThrough');
1639
2489
  }
@@ -1740,8 +2590,18 @@ class NgxRichTextEditorComponent {
1740
2590
  setFontFamily(font) {
1741
2591
  if (this.disabled || this.readonly || this.isSourceView || !font)
1742
2592
  return;
1743
- this.focusEditor();
1744
- this.commandService.setFontName(font);
2593
+ const selection = typeof window !== 'undefined' ? window.getSelection() : null;
2594
+ const hasSelection = selection && !selection.isCollapsed && (selection.toString()?.length ?? 0) > 0;
2595
+ if (hasSelection) {
2596
+ this.focusEditor();
2597
+ this.commandService.setFontName(font);
2598
+ }
2599
+ else {
2600
+ this.fontFamily = font;
2601
+ if (this.editorContentRef?.nativeElement) {
2602
+ this.editorContentRef.nativeElement.style.fontFamily = font;
2603
+ }
2604
+ }
1745
2605
  this.onEditorInput();
1746
2606
  this.updateEditorState();
1747
2607
  }
@@ -1751,8 +2611,18 @@ class NgxRichTextEditorComponent {
1751
2611
  setFontSize(size) {
1752
2612
  if (this.disabled || this.readonly || this.isSourceView || !size)
1753
2613
  return;
1754
- this.focusEditor();
1755
- this.commandService.setFontSize(String(size));
2614
+ const selection = typeof window !== 'undefined' ? window.getSelection() : null;
2615
+ const hasSelection = selection && !selection.isCollapsed && (selection.toString()?.length ?? 0) > 0;
2616
+ if (hasSelection) {
2617
+ this.focusEditor();
2618
+ this.commandService.setFontSize(String(size));
2619
+ }
2620
+ else {
2621
+ this.fontSize = String(size);
2622
+ if (this.editorContentRef?.nativeElement) {
2623
+ this.editorContentRef.nativeElement.style.fontSize = String(size);
2624
+ }
2625
+ }
1756
2626
  this.onEditorInput();
1757
2627
  this.updateEditorState();
1758
2628
  }
@@ -2040,7 +2910,7 @@ class NgxRichTextEditorComponent {
2040
2910
  this.focusEditor();
2041
2911
  }
2042
2912
  getHtml() {
2043
- return this.content;
2913
+ return this.editorContentRef?.nativeElement?.innerHTML || this.content;
2044
2914
  }
2045
2915
  getText() {
2046
2916
  return this.editorContentRef?.nativeElement?.innerText || '';
@@ -2199,12 +3069,36 @@ class NgxRichTextEditorComponent {
2199
3069
  let italic = this.commandService.queryState('italic');
2200
3070
  let underline = this.commandService.queryState('underline');
2201
3071
  if (this.exclusiveInlineFormats ?? this.computedConfig.exclusiveInlineFormats) {
2202
- if (underline) {
2203
- bold = false;
2204
- italic = false;
3072
+ const sel = typeof window !== 'undefined' ? window.getSelection() : null;
3073
+ const root = this.editorContentRef?.nativeElement;
3074
+ let formatAncestor = null;
3075
+ if (sel && sel.rangeCount > 0 && root) {
3076
+ const range = sel.getRangeAt(0);
3077
+ if (root.contains(range.commonAncestorContainer)) {
3078
+ formatAncestor = this.getFormattingAncestor(range, root);
3079
+ }
2205
3080
  }
2206
- else if (bold) {
2207
- italic = false;
3081
+ if (formatAncestor) {
3082
+ const type = this.getFormatType(formatAncestor);
3083
+ bold = type === 'bold';
3084
+ italic = type === 'italic';
3085
+ underline = type === 'underline';
3086
+ }
3087
+ else {
3088
+ if (sel && sel.rangeCount > 0 && !sel.getRangeAt(0).collapsed) {
3089
+ bold = false;
3090
+ italic = false;
3091
+ underline = false;
3092
+ }
3093
+ else {
3094
+ if (underline) {
3095
+ bold = false;
3096
+ italic = false;
3097
+ }
3098
+ else if (bold) {
3099
+ italic = false;
3100
+ }
3101
+ }
2208
3102
  }
2209
3103
  }
2210
3104
  this.activeStates = {
@@ -2239,14 +3133,26 @@ class NgxRichTextEditorComponent {
2239
3133
  const el = element || this.editorContentRef?.nativeElement;
2240
3134
  if (!el)
2241
3135
  return '';
2242
- return this.serializeChildren(el).replace(/\n+$/g, '');
3136
+ const md = this.serializeChildren(el, el).replace(/\n$/, '');
3137
+ return md;
2243
3138
  }
2244
- serializeChildren(element) {
3139
+ isBlockElement(node, editorRoot) {
3140
+ if (node.nodeType !== Node.ELEMENT_NODE)
3141
+ return false;
3142
+ const el = node;
3143
+ const tag = el.tagName.toLowerCase();
3144
+ const blockTags = [
3145
+ 'p', 'div', 'ul', 'ol', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
3146
+ 'blockquote', 'pre', 'section', 'article', 'header', 'footer', 'hr'
3147
+ ];
3148
+ return blockTags.includes(tag);
3149
+ }
3150
+ serializeChildren(element, editorRoot) {
2245
3151
  let output = '';
3152
+ const root = editorRoot || (element === this.editorContentRef?.nativeElement ? element : this.editorContentRef?.nativeElement);
2246
3153
  for (const node of Array.from(element.childNodes)) {
2247
- const serialized = this.serializeNode(node);
2248
- const isBlock = node.nodeType === Node.ELEMENT_NODE &&
2249
- ['p', 'div', 'ul', 'ol', 'h1', 'h2', 'h3', 'h4', 'blockquote', 'pre'].includes(node.tagName.toLowerCase());
3154
+ const serialized = this.serializeNode(node, root);
3155
+ const isBlock = this.isBlockElement(node, root);
2250
3156
  if (isBlock && output.length > 0 && !output.endsWith('\n')) {
2251
3157
  output += '\n';
2252
3158
  }
@@ -2254,30 +3160,36 @@ class NgxRichTextEditorComponent {
2254
3160
  }
2255
3161
  return output;
2256
3162
  }
2257
- serializeNode(node) {
3163
+ serializeNode(node, editorRoot) {
2258
3164
  if (node.nodeType === Node.TEXT_NODE) {
2259
- const text = node.textContent ?? '';
2260
- return /^[\n\r\t ]+$/.test(text) && /[\n\r\t]/.test(text) ? '' : text;
3165
+ const text = (node.textContent ?? '').replace(/\r\n?/g, '\n');
3166
+ if (editorRoot && node.parentElement) {
3167
+ const hasBlockSiblings = Array.from(node.parentElement.childNodes).some((sibling) => sibling !== node && this.isBlockElement(sibling, editorRoot));
3168
+ if (hasBlockSiblings && /^[\n\r\t ]+$/.test(text)) {
3169
+ return '';
3170
+ }
3171
+ }
3172
+ return text;
2261
3173
  }
2262
3174
  if (node.nodeType !== Node.ELEMENT_NODE) {
2263
3175
  return '';
2264
3176
  }
2265
3177
  const element = node;
2266
3178
  const tag = element.tagName.toLowerCase();
2267
- const children = this.serializeChildren(element);
3179
+ const children = this.serializeChildren(element, editorRoot);
2268
3180
  // Hashtag span: extract raw hashtag text (#tag)
2269
3181
  if (element.classList.contains('ngx-editor-hashtag') || element.classList.contains('post-content-hashtag')) {
2270
3182
  return element.textContent?.trim() || '';
2271
3183
  }
2272
3184
  // Exclusive single effect precedence: Underline > Bold > Italic
2273
3185
  if (this.isUnderlineElement(element)) {
2274
- return this.hasVisibleText(element) ? this.wrapMarkdown(children, '__') : '';
3186
+ return this.wrapMarkdown(children, '__');
2275
3187
  }
2276
3188
  if (this.isBoldElement(element)) {
2277
- return this.hasVisibleText(element) ? this.wrapMarkdown(children, '**') : '';
3189
+ return this.wrapMarkdown(children, '**');
2278
3190
  }
2279
3191
  if (this.isItalicElement(element)) {
2280
- return this.hasVisibleText(element) ? this.wrapMarkdown(children, '*') : '';
3192
+ return this.wrapMarkdown(children, '*');
2281
3193
  }
2282
3194
  if (tag === 'br') {
2283
3195
  return '\n';
@@ -2293,12 +3205,27 @@ class NgxRichTextEditorComponent {
2293
3205
  if (tag === 'li') {
2294
3206
  return children;
2295
3207
  }
2296
- if (tag === 'p' || tag === 'div') {
3208
+ if (tag === 'p' || tag === 'div' || this.isBlockElement(element, editorRoot)) {
2297
3209
  return children.endsWith('\n') ? children : `${children}\n`;
2298
3210
  }
2299
3211
  return children;
2300
3212
  }
2301
3213
  wrapMarkdown(text, marker) {
3214
+ if (!text)
3215
+ return '';
3216
+ // If text contains newlines, wrap each line individually so markdown formatting
3217
+ // does not span across paragraph/line breaks (which breaks markdown line parsers)
3218
+ if (text.includes('\n')) {
3219
+ return text
3220
+ .split('\n')
3221
+ .map((line) => this.wrapMarkdownSingleLine(line, marker))
3222
+ .join('\n');
3223
+ }
3224
+ return this.wrapMarkdownSingleLine(text, marker);
3225
+ }
3226
+ wrapMarkdownSingleLine(text, marker) {
3227
+ if (!text)
3228
+ return '';
2302
3229
  let clean = text;
2303
3230
  // Mutually exclusive formatting: strip conflicting markers so only ONE effect is applied
2304
3231
  if (marker === '__') {
@@ -2688,6 +3615,10 @@ class NgxRichTextEditorComponent {
2688
3615
  focusEditor() {
2689
3616
  if (this.editorContentRef && this.editorContentRef.nativeElement) {
2690
3617
  this.editorContentRef.nativeElement.focus();
3618
+ try {
3619
+ document.execCommand('defaultParagraphSeparator', false, 'p');
3620
+ }
3621
+ catch { }
2691
3622
  }
2692
3623
  }
2693
3624
  onDocumentClick(event) {
@@ -2702,7 +3633,7 @@ class NgxRichTextEditorComponent {
2702
3633
  useExisting: forwardRef(() => NgxRichTextEditorComponent),
2703
3634
  multi: true,
2704
3635
  },
2705
- ], viewQueries: [{ propertyName: "editorContentRef", first: true, predicate: ["editorContent"], descendants: true }, { propertyName: "sourceTextareaRef", first: true, predicate: ["sourceTextarea"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n class=\"ngx-editor-container\"\n [ngClass]=\"customClass\"\n [class.is-focused]=\"isFocused\"\n [class.is-disabled]=\"disabled\"\n [style.min-height]=\"computedConfig.minHeight\"\n [style.max-height]=\"computedConfig.maxHeight\"\n [style.font-family]=\"fontFamily || computedConfig.fontFamily || null\"\n [style.font-size]=\"fontSize || computedConfig.fontSize || null\"\n [ngStyle]=\"parsedEditorStyle\"\n>\n <!-- Toolbar -->\n @if (computedConfig.showToolbar && !readonly) {\n <!-- Array-configured Toolbar (if tools input is provided) -->\n @if (computedConfig.tools && computedConfig.tools.length > 0) {\n <ngx-editor-toolbar\n [editor]=\"this\"\n [tools]=\"computedConfig.tools\"\n [fontFamilies]=\"resolvedFontFamilies\"\n ></ngx-editor-toolbar>\n } @else {\n <!-- Default Full Toolbar -->\n <div class=\"ngx-editor-toolbar\" role=\"toolbar\" aria-label=\"Editor Toolbar\">\n <!-- History -->\n @if (computedConfig.toolbar?.history) {\n <div class=\"ngx-editor-toolbar-group\">\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Undo (Ctrl+Z)\"\n aria-label=\"Undo\"\n (mousedown)=\"$event.preventDefault(); undo()\"\n >\n <ngx-editor-icon name=\"undo\"></ngx-editor-icon>\n </button>\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Redo (Ctrl+Y)\"\n aria-label=\"Redo\"\n (mousedown)=\"$event.preventDefault(); redo()\"\n >\n <ngx-editor-icon name=\"redo\"></ngx-editor-icon>\n </button>\n </div>\n }\n\n <!-- Format Block -->\n @if (computedConfig.toolbar?.format) {\n <div class=\"ngx-editor-toolbar-group\">\n <select\n class=\"ngx-editor-select\"\n title=\"Paragraph Format\"\n aria-label=\"Paragraph Format\"\n [value]=\"activeBlockTag\"\n (change)=\"onFormatBlockChange($event)\"\n >\n <option value=\"p\">Normal text</option>\n <option value=\"h1\">Heading 1</option>\n <option value=\"h2\">Heading 2</option>\n <option value=\"h3\">Heading 3</option>\n <option value=\"h4\">Heading 4</option>\n <option value=\"blockquote\">Quote</option>\n <option value=\"pre\">Code Block</option>\n </select>\n </div>\n }\n\n <!-- Font Family -->\n @if (computedConfig.toolbar?.fontName) {\n <div class=\"ngx-editor-toolbar-group\">\n <select\n class=\"ngx-editor-select\"\n title=\"Font Family\"\n aria-label=\"Font Family\"\n (change)=\"onFontNameChange($event)\"\n >\n <option value=\"\">Font</option>\n @for (font of resolvedFontFamilies; track font.value) {\n <option [value]=\"font.value\">{{ font.label }}</option>\n }\n </select>\n </div>\n }\n\n <!-- Font Size -->\n @if (computedConfig.toolbar?.fontSize) {\n <div class=\"ngx-editor-toolbar-group\">\n <select\n class=\"ngx-editor-select\"\n title=\"Font Size\"\n aria-label=\"Font Size\"\n (change)=\"onFontSizeChange($event)\"\n >\n <option value=\"\">Size</option>\n <option value=\"1\">Small (10pt)</option>\n <option value=\"3\">Normal (12pt)</option>\n <option value=\"5\">Large (18pt)</option>\n <option value=\"7\">Huge (36pt)</option>\n </select>\n </div>\n }\n\n <!-- Inline Styles: Bold, Italic, Underline, Strike, Sub, Sup, Code -->\n @if (computedConfig.toolbar?.inline) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.inline?.bold !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.bold\"\n title=\"Bold (Ctrl+B)\"\n aria-label=\"Bold\"\n (mousedown)=\"$event.preventDefault(); bold()\"\n >\n <ngx-editor-icon name=\"bold\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.italic !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.italic\"\n title=\"Italic (Ctrl+I)\"\n aria-label=\"Italic\"\n (mousedown)=\"$event.preventDefault(); italic()\"\n >\n <ngx-editor-icon name=\"italic\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.underline !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.underline\"\n title=\"Underline (Ctrl+U)\"\n aria-label=\"Underline\"\n (mousedown)=\"$event.preventDefault(); underline()\"\n >\n <ngx-editor-icon name=\"underline\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.strikeThrough !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.strikeThrough\"\n title=\"Strikethrough\"\n aria-label=\"Strikethrough\"\n (mousedown)=\"$event.preventDefault(); strike()\"\n >\n <ngx-editor-icon name=\"strike\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.subscript !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.subscript\"\n title=\"Subscript\"\n aria-label=\"Subscript\"\n (mousedown)=\"$event.preventDefault(); subscript()\"\n >\n <ngx-editor-icon name=\"subscript\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.superscript !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.superscript\"\n title=\"Superscript\"\n aria-label=\"Superscript\"\n (mousedown)=\"$event.preventDefault(); superscript()\"\n >\n <ngx-editor-icon name=\"superscript\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.code !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Inline Code\"\n aria-label=\"Inline Code\"\n (mousedown)=\"$event.preventDefault(); wrapSelectionWithCode()\"\n >\n <ngx-editor-icon name=\"code\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n\n <!-- Colors -->\n @if (computedConfig.toolbar?.color) {\n <div class=\"ngx-editor-toolbar-group\" style=\"position: relative;\">\n @if (computedConfig.toolbar?.color?.textColor !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn ngx-editor-color-btn\"\n title=\"Text Color\"\n aria-label=\"Text Color\"\n (click)=\"toggleColorPicker('text')\"\n >\n <ngx-editor-icon name=\"textColor\"></ngx-editor-icon>\n </button>\n <div\n class=\"color-bar\"\n [style.background-color]=\"selectedTextColor\"\n ></div>\n }\n\n @if (computedConfig.toolbar?.color?.backgroundColor !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn ngx-editor-color-btn\"\n title=\"Highlight Color\"\n aria-label=\"Highlight Color\"\n (click)=\"toggleColorPicker('background')\"\n >\n <ngx-editor-icon name=\"bgColor\"></ngx-editor-icon>\n </button>\n <div\n class=\"color-bar\"\n [style.background-color]=\"selectedBgColor\"\n ></div>\n }\n\n <!-- Color Popover -->\n @if (activeColorPicker) {\n <div class=\"ngx-color-popover\" (mousedown)=\"$event.stopPropagation()\">\n <div class=\"color-grid\">\n @for (color of computedConfig.colorPalette; track color) {\n <button\n type=\"button\"\n class=\"color-swatch\"\n [style.background-color]=\"color\"\n (click)=\"applyColor(color)\"\n ></button>\n }\n </div>\n <div class=\"custom-color-picker\">\n <input\n type=\"color\"\n (change)=\"onCustomColorChange($event)\"\n />\n <span>Custom color</span>\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Alignment -->\n @if (computedConfig.toolbar?.alignment) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.alignment?.left !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.justifyLeft\"\n title=\"Align Left\"\n aria-label=\"Align Left\"\n (mousedown)=\"$event.preventDefault(); alignLeft()\"\n >\n <ngx-editor-icon name=\"alignLeft\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.alignment?.center !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.justifyCenter\"\n title=\"Align Center\"\n aria-label=\"Align Center\"\n (mousedown)=\"$event.preventDefault(); alignCenter()\"\n >\n <ngx-editor-icon name=\"alignCenter\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.alignment?.right !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.justifyRight\"\n title=\"Align Right\"\n aria-label=\"Align Right\"\n (mousedown)=\"$event.preventDefault(); alignRight()\"\n >\n <ngx-editor-icon name=\"alignRight\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.alignment?.justify !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.justifyFull\"\n title=\"Justify\"\n aria-label=\"Justify\"\n (mousedown)=\"$event.preventDefault(); alignJustify()\"\n >\n <ngx-editor-icon name=\"alignJustify\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n\n <!-- Lists & Indentation -->\n @if (computedConfig.toolbar?.list) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.list?.unordered !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.insertUnorderedList\"\n title=\"Bullet List\"\n aria-label=\"Bullet List\"\n (mousedown)=\"$event.preventDefault(); insertUnorderedList()\"\n >\n <ngx-editor-icon name=\"unorderedList\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.list?.ordered !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.insertOrderedList\"\n title=\"Numbered List\"\n aria-label=\"Numbered List\"\n (mousedown)=\"$event.preventDefault(); insertOrderedList()\"\n >\n <ngx-editor-icon name=\"orderedList\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.list?.indent !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Increase Indent\"\n aria-label=\"Increase Indent\"\n (mousedown)=\"$event.preventDefault(); indent()\"\n >\n <ngx-editor-icon name=\"indent\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.list?.outdent !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Decrease Indent\"\n aria-label=\"Decrease Indent\"\n (mousedown)=\"$event.preventDefault(); outdent()\"\n >\n <ngx-editor-icon name=\"outdent\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n\n <!-- Insert Options -->\n @if (computedConfig.toolbar?.insert) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.insert?.link !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.link\"\n title=\"Insert Link (Ctrl+K)\"\n aria-label=\"Insert Link\"\n (click)=\"openLinkModal()\"\n >\n <ngx-editor-icon name=\"link\"></ngx-editor-icon>\n </button>\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Remove Link\"\n aria-label=\"Remove Link\"\n (mousedown)=\"$event.preventDefault(); unlink()\"\n >\n <ngx-editor-icon name=\"unlink\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.insert?.image !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Insert Image\"\n aria-label=\"Insert Image\"\n (click)=\"openImageModal()\"\n >\n <ngx-editor-icon name=\"image\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.insert?.horizontalRule !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Horizontal Line\"\n aria-label=\"Horizontal Line\"\n (mousedown)=\"$event.preventDefault(); insertHorizontalRule()\"\n >\n <ngx-editor-icon name=\"horizontalRule\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n\n <!-- Extra Actions -->\n @if (computedConfig.toolbar?.extra) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.extra?.clearFormat !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Clear Formatting\"\n aria-label=\"Clear Formatting\"\n (mousedown)=\"$event.preventDefault(); clearFormatting()\"\n >\n <ngx-editor-icon name=\"clearFormat\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.extra?.sourceView !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"isSourceView\"\n title=\"View HTML Source\"\n aria-label=\"View HTML Source\"\n (click)=\"toggleSourceView()\"\n >\n <ngx-editor-icon name=\"sourceView\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n </div>\n }\n }\n\n <!-- Editor Content Area -->\n <div class=\"ngx-editor-content-wrapper\">\n <!-- Visual contenteditable -->\n <div\n #editorContent\n class=\"ngx-editor-content\"\n [style.display]=\"isSourceView ? 'none' : 'block'\"\n [style.height]=\"computedConfig.height\"\n [style.font-family]=\"fontFamily || computedConfig.fontFamily || null\"\n [style.font-size]=\"fontSize || computedConfig.fontSize || null\"\n [ngStyle]=\"parsedContentStyle\"\n [attr.contenteditable]=\"!disabled && !readonly\"\n [attr.spellcheck]=\"computedConfig.spellcheck\"\n [attr.data-placeholder]=\"computedConfig.placeholder\"\n [attr.data-empty]=\"isEmpty\"\n (input)=\"onEditorInput()\"\n (focus)=\"onEditorFocus($event)\"\n (blur)=\"onEditorBlur($event)\"\n (keydown)=\"onEditorKeydown($event)\"\n (keyup)=\"updateEditorState()\"\n (mouseup)=\"updateEditorState()\"\n (paste)=\"onEditorPaste($event)\"\n ></div>\n\n <!-- Raw HTML Source Code View -->\n @if (isSourceView) {\n <textarea\n #sourceTextarea\n class=\"ngx-editor-source\"\n [style.height]=\"computedConfig.height\"\n [disabled]=\"disabled || readonly\"\n [value]=\"content\"\n (input)=\"onSourceInput($event)\"\n ></textarea>\n }\n </div>\n\n <!-- Status Bar -->\n @if (computedConfig.showStatusBar) {\n <div class=\"ngx-editor-statusbar\">\n <div class=\"statusbar-left\">\n @if (activeBlockTag) {\n <span class=\"status-tag\">&lt;{{ activeBlockTag }}&gt;</span>\n }\n @if (isSourceView) {\n <span class=\"status-badge\">HTML SOURCE MODE</span>\n }\n </div>\n <div class=\"statusbar-right\">\n @if (computedConfig.showWordCount) {\n <span>Words: <strong>{{ stats.words }}</strong></span>\n }\n @if (computedConfig.maxCharacters) {\n <span [class.limit-reached]=\"stats.chars > computedConfig.maxCharacters\">\n {{ stats.chars }}/{{ computedConfig.maxCharacters }} Characters\n </span>\n } @else if (computedConfig.showCharCount) {\n <span>Characters: <strong>{{ stats.chars }}</strong></span>\n }\n </div>\n </div>\n }\n\n <!-- Link Modal -->\n @if (showLinkModal) {\n <div class=\"ngx-editor-modal-backdrop\" (click)=\"closeLinkModal()\">\n <div class=\"ngx-editor-modal\" (click)=\"$event.stopPropagation()\">\n <div class=\"modal-header\">\n <h3>Insert Link</h3>\n <button type=\"button\" class=\"ngx-editor-btn\" (click)=\"closeLinkModal()\" aria-label=\"Close\">\n <ngx-editor-icon name=\"close\"></ngx-editor-icon>\n </button>\n </div>\n <div class=\"modal-body\">\n <div class=\"form-group\">\n <label>Link Text</label>\n <input\n type=\"text\"\n placeholder=\"Display text\"\n [(ngModel)]=\"linkModel.text\"\n />\n </div>\n <div class=\"form-group\">\n <label>Target URL</label>\n <input\n type=\"url\"\n placeholder=\"https://example.com\"\n [(ngModel)]=\"linkModel.url\"\n (keydown.enter)=\"insertLinkFromModal()\"\n />\n </div>\n <div class=\"form-group\">\n <label class=\"checkbox-label\">\n <input type=\"checkbox\" [(ngModel)]=\"linkModel.newTab\" />\n Open in a new tab\n </label>\n </div>\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn-secondary\" (click)=\"closeLinkModal()\">Cancel</button>\n <button\n type=\"button\"\n class=\"btn-primary\"\n [disabled]=\"!linkModel.url\"\n (click)=\"insertLinkFromModal()\"\n >\n Insert Link\n </button>\n </div>\n </div>\n </div>\n }\n\n <!-- Image Modal -->\n @if (showImageModal) {\n <div class=\"ngx-editor-modal-backdrop\" (click)=\"closeImageModal()\">\n <div class=\"ngx-editor-modal\" (click)=\"$event.stopPropagation()\">\n <div class=\"modal-header\">\n <h3>Insert Image</h3>\n <button type=\"button\" class=\"ngx-editor-btn\" (click)=\"closeImageModal()\" aria-label=\"Close\">\n <ngx-editor-icon name=\"close\"></ngx-editor-icon>\n </button>\n </div>\n <div class=\"modal-body\">\n <div class=\"form-group\">\n <label>Image URL</label>\n <input\n type=\"url\"\n placeholder=\"https://example.com/image.png\"\n [(ngModel)]=\"imageModel.url\"\n />\n </div>\n <div class=\"form-group\">\n <label>Or Upload File</label>\n <input\n type=\"file\"\n accept=\"image/*\"\n (change)=\"onImageFileUpload($event)\"\n />\n </div>\n <div class=\"form-group\">\n <label>Alt Description</label>\n <input\n type=\"text\"\n placeholder=\"Image description\"\n [(ngModel)]=\"imageModel.alt\"\n />\n </div>\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn-secondary\" (click)=\"closeImageModal()\">Cancel</button>\n <button\n type=\"button\"\n class=\"btn-primary\"\n [disabled]=\"!imageModel.url\"\n (click)=\"insertImageFromModal()\"\n >\n Insert Image\n </button>\n </div>\n </div>\n </div>\n }\n</div>\n", styles: [":host{display:block;font-family:var(--ngx-editor-font-family, inherit);box-sizing:border-box;--ngx-editor-font-family: inherit;--ngx-editor-font-size: 15px;--ngx-editor-line-height: 1.65;--ngx-editor-border: #e2e8f0;--ngx-editor-border-focus: #3b82f6;--ngx-editor-bg: #ffffff;--ngx-editor-toolbar-bg: #f8fafc;--ngx-editor-toolbar-btn-hover: #e2e8f0;--ngx-editor-toolbar-btn-active: #dbeafe;--ngx-editor-toolbar-btn-active-color: #1d4ed8;--ngx-editor-text: #1e293b;--ngx-editor-muted: #64748b;--ngx-editor-radius: 8px;--ngx-editor-shadow: 0 1px 3px 0 rgba(0, 0, 0, .1), 0 1px 2px -1px rgba(0, 0, 0, .1);--ngx-editor-shadow-focus: 0 0 0 3px rgba(59, 130, 246, .25);--ngx-editor-hashtag-color: #0069FF}.ngx-editor-container{display:flex;flex-direction:column;background-color:var(--ngx-editor-bg);border:1px solid var(--ngx-editor-border);border-radius:var(--ngx-editor-radius);box-shadow:var(--ngx-editor-shadow);overflow:hidden;transition:border-color .2s ease,box-shadow .2s ease;position:relative}.ngx-editor-container.is-focused{border-color:var(--ngx-editor-border-focus);box-shadow:var(--ngx-editor-shadow-focus)}.ngx-editor-container.is-disabled{background-color:#f1f5f9;opacity:.75;pointer-events:none;-webkit-user-select:none;user-select:none}.ngx-editor-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:4px;padding:6px 8px;background-color:var(--ngx-editor-toolbar-bg);border-bottom:1px solid var(--ngx-editor-border);-webkit-user-select:none;user-select:none;z-index:10}.ngx-editor-toolbar-group{display:flex;align-items:center;gap:2px}.ngx-editor-toolbar-group:not(:last-child):after{content:\"\";display:inline-block;width:1px;height:18px;background-color:var(--ngx-editor-border);margin:0 4px}.ngx-editor-btn{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;padding:0;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--ngx-editor-text);cursor:pointer;transition:background-color .15s ease,color .15s ease,border-color .15s ease;outline:none}.ngx-editor-btn:hover:not(:disabled){background-color:var(--ngx-editor-toolbar-btn-hover)}.ngx-editor-btn:active:not(:disabled),.ngx-editor-btn.is-active{background-color:var(--ngx-editor-toolbar-btn-active);color:var(--ngx-editor-toolbar-btn-active-color);border-color:#bfdbfe}.ngx-editor-btn:disabled{opacity:.35;cursor:not-allowed}.ngx-editor-btn svg{display:block;pointer-events:none}.ngx-editor-select{height:32px;padding:0 8px;font-size:13px;color:var(--ngx-editor-text);background-color:var(--ngx-editor-bg);border:1px solid var(--ngx-editor-border);border-radius:4px;outline:none;cursor:pointer;transition:border-color .15s ease}.ngx-editor-select:hover{border-color:#cbd5e1}.ngx-editor-select:focus{border-color:var(--ngx-editor-border-focus)}.ngx-editor-color-btn{position:relative}.ngx-editor-color-btn .color-bar{position:absolute;bottom:4px;left:6px;right:6px;height:3px;border-radius:1px}.ngx-color-popover{position:absolute;top:100%;left:0;margin-top:4px;background:#fff;border:1px solid var(--ngx-editor-border);border-radius:6px;box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;padding:8px;z-index:50;width:176px}.ngx-color-popover .color-grid{display:grid;grid-template-columns:repeat(6,1fr);gap:4px;margin-bottom:8px}.ngx-color-popover .color-swatch{width:22px;height:22px;border-radius:3px;border:1px solid rgba(0,0,0,.15);cursor:pointer;padding:0;transition:transform .1s ease}.ngx-color-popover .color-swatch:hover{transform:scale(1.15);z-index:2}.ngx-color-popover .custom-color-picker{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--ngx-editor-muted)}.ngx-color-popover .custom-color-picker input[type=color]{width:28px;height:24px;border:1px solid var(--ngx-editor-border);border-radius:3px;padding:0;cursor:pointer;background:none}.ngx-editor-content-wrapper{position:relative;flex:1;display:flex;flex-direction:column}.ngx-editor-content{flex:1;padding:16px;outline:none;font-family:var(--ngx-editor-font-family, inherit);font-size:var(--ngx-editor-font-size, 15px);line-height:var(--ngx-editor-line-height, 1.65);color:var(--ngx-editor-text);overflow-y:auto;min-height:200px;word-break:break-word}.ngx-editor-content p{margin:0 0 1em}.ngx-editor-content b,.ngx-editor-content strong{font-weight:700!important}.ngx-editor-content i,.ngx-editor-content em{font-style:italic!important}.ngx-editor-content u,.ngx-editor-content .publish-editor-underline{text-decoration:underline!important;text-decoration-thickness:1px;text-underline-offset:.12em}.ngx-editor-content h1{font-size:2em;font-weight:700;margin:.67em 0;line-height:1.25}.ngx-editor-content h2{font-size:1.6em;font-weight:600;margin:.75em 0;line-height:1.3}.ngx-editor-content h3{font-size:1.3em;font-weight:600;margin:.83em 0}.ngx-editor-content h4{font-size:1.1em;font-weight:600;margin:1em 0}.ngx-editor-content ul,.ngx-editor-content ol{margin:0 0 1em 1.5em;padding:0}.ngx-editor-content li{margin-bottom:.25em}.ngx-editor-content blockquote{margin:1em 0;padding:.5em 1em;border-left:4px solid var(--ngx-editor-border-focus);background-color:#f8fafc;color:var(--ngx-editor-muted);font-style:italic}.ngx-editor-content pre{background-color:#1e293b;color:#f8fafc;padding:12px;border-radius:6px;font-family:Consolas,Monaco,Courier New,monospace;font-size:13px;overflow-x:auto;margin:1em 0}.ngx-editor-content code{background-color:#f1f5f9;color:#e11d48;padding:2px 5px;border-radius:4px;font-family:Consolas,Monaco,Courier New,monospace;font-size:.9em}.ngx-editor-content a{color:#2563eb;text-decoration:underline;text-underline-offset:2px;cursor:pointer}.ngx-editor-content .ngx-editor-hashtag,.ngx-editor-content .post-content-hashtag{color:var(--ngx-editor-hashtag-color, #0069FF)!important;font-weight:500;display:inline;cursor:text}.ngx-editor-content hr{border:none;border-top:1px solid var(--ngx-editor-border);margin:1.5em 0}.ngx-editor-content img{max-width:100%;height:auto;border-radius:4px}.ngx-editor-content[data-empty=true]:before{content:attr(data-placeholder);position:absolute;color:#94a3b8;pointer-events:none;-webkit-user-select:none;user-select:none}.ngx-editor-content:has(ol,ul,li,table,img,hr,blockquote,pre):before{display:none!important;content:none!important}.ngx-editor-source{width:100%;flex:1;min-height:200px;padding:16px;border:none;outline:none;font-family:Consolas,Monaco,Courier New,monospace;font-size:13.5px;line-height:1.5;background-color:#0f172a;color:#38bdf8;resize:vertical;box-sizing:border-box}.ngx-editor-statusbar{display:flex;align-items:center;justify-content:space-between;padding:6px 12px;background-color:var(--ngx-editor-toolbar-bg);border-top:1px solid var(--ngx-editor-border);font-size:12px;color:var(--ngx-editor-muted)}.ngx-editor-statusbar .statusbar-left,.ngx-editor-statusbar .statusbar-right{display:flex;align-items:center;gap:12px}.ngx-editor-statusbar .status-tag{background-color:#e2e8f0;padding:2px 6px;border-radius:4px;font-weight:600;text-transform:uppercase;font-size:10px}.ngx-editor-statusbar .status-badge{background-color:#fee2e2;color:#ef4444;padding:2px 6px;border-radius:4px;font-weight:500;font-size:11px}.ngx-editor-modal-backdrop{position:absolute;inset:0;background:#0f172a66;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);display:flex;align-items:center;justify-content:center;z-index:100;animation:fadeIn .15s ease-out}.ngx-editor-modal{background:#fff;border-radius:8px;box-shadow:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a;width:90%;max-width:400px;padding:20px;box-sizing:border-box}.ngx-editor-modal .modal-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px}.ngx-editor-modal .modal-header h3{margin:0;font-size:16px;font-weight:600;color:var(--ngx-editor-text)}.ngx-editor-modal .modal-body{display:flex;flex-direction:column;gap:12px}.ngx-editor-modal .form-group{display:flex;flex-direction:column;gap:4px}.ngx-editor-modal .form-group label{font-size:13px;font-weight:500;color:var(--ngx-editor-text)}.ngx-editor-modal .form-group input[type=text],.ngx-editor-modal .form-group input[type=url]{padding:8px 12px;border:1px solid var(--ngx-editor-border);border-radius:6px;font-size:14px;outline:none;transition:border-color .15s ease}.ngx-editor-modal .form-group input[type=text]:focus,.ngx-editor-modal .form-group input[type=url]:focus{border-color:var(--ngx-editor-border-focus)}.ngx-editor-modal .form-group .checkbox-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-size:13px;color:var(--ngx-editor-text);-webkit-user-select:none;user-select:none}.ngx-editor-modal .modal-footer{display:flex;justify-content:flex-end;gap:8px;margin-top:20px}.ngx-editor-modal .modal-footer button{padding:7px 14px;border-radius:6px;font-size:13px;font-weight:500;cursor:pointer;transition:all .15s ease;outline:none}.ngx-editor-modal .modal-footer .btn-secondary{background:#f1f5f9;border:1px solid var(--ngx-editor-border);color:var(--ngx-editor-text)}.ngx-editor-modal .modal-footer .btn-secondary:hover{background:#e2e8f0}.ngx-editor-modal .modal-footer .btn-primary{background:#2563eb;border:1px solid #2563eb;color:#fff}.ngx-editor-modal .modal-footer .btn-primary:hover{background:#1d4ed8}.ngx-editor-modal .modal-footer .btn-primary:disabled{opacity:.5;cursor:not-allowed}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i3.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i4.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: NgxEditorIconComponent, selector: "ngx-editor-icon", inputs: ["name"] }, { kind: "component", type: NgxEditorToolbarComponent, selector: "ngx-editor-toolbar", inputs: ["editor", "tools", "fontFamilies"], outputs: ["actionTriggered"] }] });
3636
+ ], viewQueries: [{ propertyName: "editorContentRef", first: true, predicate: ["editorContent"], descendants: true }, { propertyName: "sourceTextareaRef", first: true, predicate: ["sourceTextarea"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n class=\"ngx-editor-container\"\n [ngClass]=\"customClass\"\n [class.is-focused]=\"isFocused\"\n [class.is-disabled]=\"disabled\"\n [style.min-height]=\"computedConfig.minHeight\"\n [style.max-height]=\"computedConfig.maxHeight\"\n [style.font-family]=\"fontFamily || computedConfig.fontFamily || null\"\n [style.font-size]=\"fontSize || computedConfig.fontSize || null\"\n [ngStyle]=\"parsedEditorStyle\"\n>\n <!-- Toolbar -->\n @if (computedConfig.showToolbar && !readonly) {\n <!-- Array-configured Toolbar (if tools input is provided) -->\n @if (computedConfig.tools && computedConfig.tools.length > 0) {\n <ngx-editor-toolbar\n [editor]=\"this\"\n [tools]=\"computedConfig.tools\"\n [fontFamilies]=\"resolvedFontFamilies\"\n ></ngx-editor-toolbar>\n } @else {\n <!-- Default Full Toolbar -->\n <div class=\"ngx-editor-toolbar\" role=\"toolbar\" aria-label=\"Editor Toolbar\">\n <!-- History -->\n @if (computedConfig.toolbar?.history) {\n <div class=\"ngx-editor-toolbar-group\">\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Undo (Ctrl+Z)\"\n aria-label=\"Undo\"\n (mousedown)=\"$event.preventDefault(); undo()\"\n >\n <ngx-editor-icon name=\"undo\"></ngx-editor-icon>\n </button>\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Redo (Ctrl+Y)\"\n aria-label=\"Redo\"\n (mousedown)=\"$event.preventDefault(); redo()\"\n >\n <ngx-editor-icon name=\"redo\"></ngx-editor-icon>\n </button>\n </div>\n }\n\n <!-- Format Block -->\n @if (computedConfig.toolbar?.format) {\n <div class=\"ngx-editor-toolbar-group\">\n <select\n class=\"ngx-editor-select\"\n title=\"Paragraph Format\"\n aria-label=\"Paragraph Format\"\n [value]=\"activeBlockTag\"\n (change)=\"onFormatBlockChange($event)\"\n >\n <option value=\"p\">Normal text</option>\n <option value=\"h1\">Heading 1</option>\n <option value=\"h2\">Heading 2</option>\n <option value=\"h3\">Heading 3</option>\n <option value=\"h4\">Heading 4</option>\n <option value=\"blockquote\">Quote</option>\n <option value=\"pre\">Code Block</option>\n </select>\n </div>\n }\n\n <!-- Font Family -->\n @if (computedConfig.toolbar?.fontName) {\n <div class=\"ngx-editor-toolbar-group\">\n <select\n class=\"ngx-editor-select\"\n title=\"Font Family\"\n aria-label=\"Font Family\"\n (change)=\"onFontNameChange($event)\"\n >\n <option value=\"\">Font</option>\n @for (font of resolvedFontFamilies; track font.value) {\n <option [value]=\"font.value\">{{ font.label }}</option>\n }\n </select>\n </div>\n }\n\n <!-- Font Size -->\n @if (computedConfig.toolbar?.fontSize) {\n <div class=\"ngx-editor-toolbar-group\">\n <select\n class=\"ngx-editor-select\"\n title=\"Font Size\"\n aria-label=\"Font Size\"\n (change)=\"onFontSizeChange($event)\"\n >\n <option value=\"\">Size</option>\n <option value=\"1\">Small (10pt)</option>\n <option value=\"3\">Normal (12pt)</option>\n <option value=\"5\">Large (18pt)</option>\n <option value=\"7\">Huge (36pt)</option>\n </select>\n </div>\n }\n\n <!-- Inline Styles: Bold, Italic, Underline, Strike, Sub, Sup, Code -->\n @if (computedConfig.toolbar?.inline) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.inline?.bold !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.bold\"\n title=\"Bold (Ctrl+B)\"\n aria-label=\"Bold\"\n (mousedown)=\"$event.preventDefault(); bold()\"\n >\n <ngx-editor-icon name=\"bold\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.italic !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.italic\"\n title=\"Italic (Ctrl+I)\"\n aria-label=\"Italic\"\n (mousedown)=\"$event.preventDefault(); italic()\"\n >\n <ngx-editor-icon name=\"italic\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.underline !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.underline\"\n title=\"Underline (Ctrl+U)\"\n aria-label=\"Underline\"\n (mousedown)=\"$event.preventDefault(); underline()\"\n >\n <ngx-editor-icon name=\"underline\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.strikeThrough !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.strikeThrough\"\n title=\"Strikethrough\"\n aria-label=\"Strikethrough\"\n (mousedown)=\"$event.preventDefault(); strike()\"\n >\n <ngx-editor-icon name=\"strike\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.subscript !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.subscript\"\n title=\"Subscript\"\n aria-label=\"Subscript\"\n (mousedown)=\"$event.preventDefault(); subscript()\"\n >\n <ngx-editor-icon name=\"subscript\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.superscript !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.superscript\"\n title=\"Superscript\"\n aria-label=\"Superscript\"\n (mousedown)=\"$event.preventDefault(); superscript()\"\n >\n <ngx-editor-icon name=\"superscript\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.code !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Inline Code\"\n aria-label=\"Inline Code\"\n (mousedown)=\"$event.preventDefault(); wrapSelectionWithCode()\"\n >\n <ngx-editor-icon name=\"code\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n\n <!-- Colors -->\n @if (computedConfig.toolbar?.color) {\n <div class=\"ngx-editor-toolbar-group\" style=\"position: relative;\">\n @if (computedConfig.toolbar?.color?.textColor !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn ngx-editor-color-btn\"\n title=\"Text Color\"\n aria-label=\"Text Color\"\n (click)=\"toggleColorPicker('text')\"\n >\n <ngx-editor-icon name=\"textColor\"></ngx-editor-icon>\n </button>\n <div\n class=\"color-bar\"\n [style.background-color]=\"selectedTextColor\"\n ></div>\n }\n\n @if (computedConfig.toolbar?.color?.backgroundColor !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn ngx-editor-color-btn\"\n title=\"Highlight Color\"\n aria-label=\"Highlight Color\"\n (click)=\"toggleColorPicker('background')\"\n >\n <ngx-editor-icon name=\"bgColor\"></ngx-editor-icon>\n </button>\n <div\n class=\"color-bar\"\n [style.background-color]=\"selectedBgColor\"\n ></div>\n }\n\n <!-- Color Popover -->\n @if (activeColorPicker) {\n <div class=\"ngx-color-popover\" (mousedown)=\"$event.stopPropagation()\">\n <div class=\"color-grid\">\n @for (color of computedConfig.colorPalette; track color) {\n <button\n type=\"button\"\n class=\"color-swatch\"\n [style.background-color]=\"color\"\n (click)=\"applyColor(color)\"\n ></button>\n }\n </div>\n <div class=\"custom-color-picker\">\n <input\n type=\"color\"\n (change)=\"onCustomColorChange($event)\"\n />\n <span>Custom color</span>\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Alignment -->\n @if (computedConfig.toolbar?.alignment) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.alignment?.left !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.justifyLeft\"\n title=\"Align Left\"\n aria-label=\"Align Left\"\n (mousedown)=\"$event.preventDefault(); alignLeft()\"\n >\n <ngx-editor-icon name=\"alignLeft\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.alignment?.center !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.justifyCenter\"\n title=\"Align Center\"\n aria-label=\"Align Center\"\n (mousedown)=\"$event.preventDefault(); alignCenter()\"\n >\n <ngx-editor-icon name=\"alignCenter\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.alignment?.right !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.justifyRight\"\n title=\"Align Right\"\n aria-label=\"Align Right\"\n (mousedown)=\"$event.preventDefault(); alignRight()\"\n >\n <ngx-editor-icon name=\"alignRight\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.alignment?.justify !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.justifyFull\"\n title=\"Justify\"\n aria-label=\"Justify\"\n (mousedown)=\"$event.preventDefault(); alignJustify()\"\n >\n <ngx-editor-icon name=\"alignJustify\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n\n <!-- Lists & Indentation -->\n @if (computedConfig.toolbar?.list) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.list?.unordered !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.insertUnorderedList\"\n title=\"Bullet List\"\n aria-label=\"Bullet List\"\n (mousedown)=\"$event.preventDefault(); insertUnorderedList()\"\n >\n <ngx-editor-icon name=\"unorderedList\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.list?.ordered !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.insertOrderedList\"\n title=\"Numbered List\"\n aria-label=\"Numbered List\"\n (mousedown)=\"$event.preventDefault(); insertOrderedList()\"\n >\n <ngx-editor-icon name=\"orderedList\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.list?.indent !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Increase Indent\"\n aria-label=\"Increase Indent\"\n (mousedown)=\"$event.preventDefault(); indent()\"\n >\n <ngx-editor-icon name=\"indent\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.list?.outdent !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Decrease Indent\"\n aria-label=\"Decrease Indent\"\n (mousedown)=\"$event.preventDefault(); outdent()\"\n >\n <ngx-editor-icon name=\"outdent\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n\n <!-- Insert Options -->\n @if (computedConfig.toolbar?.insert) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.insert?.link !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.link\"\n title=\"Insert Link (Ctrl+K)\"\n aria-label=\"Insert Link\"\n (click)=\"openLinkModal()\"\n >\n <ngx-editor-icon name=\"link\"></ngx-editor-icon>\n </button>\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Remove Link\"\n aria-label=\"Remove Link\"\n (mousedown)=\"$event.preventDefault(); unlink()\"\n >\n <ngx-editor-icon name=\"unlink\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.insert?.image !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Insert Image\"\n aria-label=\"Insert Image\"\n (click)=\"openImageModal()\"\n >\n <ngx-editor-icon name=\"image\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.insert?.horizontalRule !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Horizontal Line\"\n aria-label=\"Horizontal Line\"\n (mousedown)=\"$event.preventDefault(); insertHorizontalRule()\"\n >\n <ngx-editor-icon name=\"horizontalRule\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n\n <!-- Extra Actions -->\n @if (computedConfig.toolbar?.extra) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.extra?.clearFormat !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Clear Formatting\"\n aria-label=\"Clear Formatting\"\n (mousedown)=\"$event.preventDefault(); clearFormatting()\"\n >\n <ngx-editor-icon name=\"clearFormat\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.extra?.sourceView !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"isSourceView\"\n title=\"View HTML Source\"\n aria-label=\"View HTML Source\"\n (click)=\"toggleSourceView()\"\n >\n <ngx-editor-icon name=\"sourceView\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n </div>\n }\n }\n\n <!-- Editor Content Area -->\n <div class=\"ngx-editor-content-wrapper\">\n <!-- Visual contenteditable -->\n <div\n #editorContent\n class=\"ngx-editor-content\"\n [style.display]=\"isSourceView ? 'none' : 'block'\"\n [style.height]=\"computedConfig.height\"\n [style.font-family]=\"fontFamily || computedConfig.fontFamily || null\"\n [style.font-size]=\"fontSize || computedConfig.fontSize || null\"\n [ngStyle]=\"parsedContentStyle\"\n [attr.contenteditable]=\"!disabled && !readonly\"\n [attr.spellcheck]=\"computedConfig.spellcheck\"\n [attr.data-placeholder]=\"computedConfig.placeholder\"\n [attr.data-empty]=\"isEmpty\"\n (input)=\"onEditorInput()\"\n (focus)=\"onEditorFocus($event)\"\n (blur)=\"onEditorBlur($event)\"\n (keydown)=\"onEditorKeydown($event)\"\n (keyup)=\"updateEditorState()\"\n (mouseup)=\"updateEditorState()\"\n (paste)=\"onEditorPaste($event)\"\n ></div>\n\n <!-- Raw HTML Source Code View -->\n @if (isSourceView) {\n <textarea\n #sourceTextarea\n class=\"ngx-editor-source\"\n [style.height]=\"computedConfig.height\"\n [disabled]=\"disabled || readonly\"\n [value]=\"content\"\n (input)=\"onSourceInput($event)\"\n ></textarea>\n }\n </div>\n\n <!-- Status Bar -->\n @if (computedConfig.showStatusBar) {\n <div class=\"ngx-editor-statusbar\">\n <div class=\"statusbar-left\">\n @if (activeBlockTag) {\n <span class=\"status-tag\">&lt;{{ activeBlockTag }}&gt;</span>\n }\n @if (isSourceView) {\n <span class=\"status-badge\">HTML SOURCE MODE</span>\n }\n </div>\n <div class=\"statusbar-right\">\n @if (computedConfig.showWordCount) {\n <span>Words: <strong>{{ stats.words }}</strong></span>\n }\n @if (computedConfig.maxCharacters) {\n <span [class.limit-reached]=\"stats.chars > computedConfig.maxCharacters\">\n {{ stats.chars }}/{{ computedConfig.maxCharacters }} Characters\n </span>\n } @else if (computedConfig.showCharCount) {\n <span>Characters: <strong>{{ stats.chars }}</strong></span>\n }\n </div>\n </div>\n }\n\n <!-- Link Modal -->\n @if (showLinkModal) {\n <div class=\"ngx-editor-modal-backdrop\" (click)=\"closeLinkModal()\">\n <div class=\"ngx-editor-modal\" (click)=\"$event.stopPropagation()\">\n <div class=\"modal-header\">\n <h3>Insert Link</h3>\n <button type=\"button\" class=\"ngx-editor-btn\" (click)=\"closeLinkModal()\" aria-label=\"Close\">\n <ngx-editor-icon name=\"close\"></ngx-editor-icon>\n </button>\n </div>\n <div class=\"modal-body\">\n <div class=\"form-group\">\n <label>Link Text</label>\n <input\n type=\"text\"\n placeholder=\"Display text\"\n [(ngModel)]=\"linkModel.text\"\n />\n </div>\n <div class=\"form-group\">\n <label>Target URL</label>\n <input\n type=\"url\"\n placeholder=\"https://example.com\"\n [(ngModel)]=\"linkModel.url\"\n (keydown.enter)=\"insertLinkFromModal()\"\n />\n </div>\n <div class=\"form-group\">\n <label class=\"checkbox-label\">\n <input type=\"checkbox\" [(ngModel)]=\"linkModel.newTab\" />\n Open in a new tab\n </label>\n </div>\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn-secondary\" (click)=\"closeLinkModal()\">Cancel</button>\n <button\n type=\"button\"\n class=\"btn-primary\"\n [disabled]=\"!linkModel.url\"\n (click)=\"insertLinkFromModal()\"\n >\n Insert Link\n </button>\n </div>\n </div>\n </div>\n }\n\n <!-- Image Modal -->\n @if (showImageModal) {\n <div class=\"ngx-editor-modal-backdrop\" (click)=\"closeImageModal()\">\n <div class=\"ngx-editor-modal\" (click)=\"$event.stopPropagation()\">\n <div class=\"modal-header\">\n <h3>Insert Image</h3>\n <button type=\"button\" class=\"ngx-editor-btn\" (click)=\"closeImageModal()\" aria-label=\"Close\">\n <ngx-editor-icon name=\"close\"></ngx-editor-icon>\n </button>\n </div>\n <div class=\"modal-body\">\n <div class=\"form-group\">\n <label>Image URL</label>\n <input\n type=\"url\"\n placeholder=\"https://example.com/image.png\"\n [(ngModel)]=\"imageModel.url\"\n />\n </div>\n <div class=\"form-group\">\n <label>Or Upload File</label>\n <input\n type=\"file\"\n accept=\"image/*\"\n (change)=\"onImageFileUpload($event)\"\n />\n </div>\n <div class=\"form-group\">\n <label>Alt Description</label>\n <input\n type=\"text\"\n placeholder=\"Image description\"\n [(ngModel)]=\"imageModel.alt\"\n />\n </div>\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn-secondary\" (click)=\"closeImageModal()\">Cancel</button>\n <button\n type=\"button\"\n class=\"btn-primary\"\n [disabled]=\"!imageModel.url\"\n (click)=\"insertImageFromModal()\"\n >\n Insert Image\n </button>\n </div>\n </div>\n </div>\n }\n</div>\n", styles: [":host{display:block;font-family:var(--ngx-editor-font-family, inherit);box-sizing:border-box;--ngx-editor-font-family: inherit;--ngx-editor-font-size: 15px;--ngx-editor-line-height: 1.65;--ngx-editor-border: #e2e8f0;--ngx-editor-border-focus: #3b82f6;--ngx-editor-bg: #ffffff;--ngx-editor-toolbar-bg: #f8fafc;--ngx-editor-toolbar-btn-hover: #e2e8f0;--ngx-editor-toolbar-btn-active: #dbeafe;--ngx-editor-toolbar-btn-active-color: #1d4ed8;--ngx-editor-text: #1e293b;--ngx-editor-muted: #64748b;--ngx-editor-radius: 8px;--ngx-editor-shadow: 0 1px 3px 0 rgba(0, 0, 0, .1), 0 1px 2px -1px rgba(0, 0, 0, .1);--ngx-editor-shadow-focus: 0 0 0 3px rgba(59, 130, 246, .25);--ngx-editor-hashtag-color: #0069FF}.ngx-editor-container{display:flex;flex-direction:column;background-color:var(--ngx-editor-bg);border:1px solid var(--ngx-editor-border);border-radius:var(--ngx-editor-radius);box-shadow:var(--ngx-editor-shadow);overflow:hidden;transition:border-color .2s ease,box-shadow .2s ease;position:relative}.ngx-editor-container.is-focused{border-color:var(--ngx-editor-border-focus);box-shadow:var(--ngx-editor-shadow-focus)}.ngx-editor-container.is-disabled{background-color:#f1f5f9;opacity:.75;pointer-events:none;-webkit-user-select:none;user-select:none}.ngx-editor-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:4px;padding:6px 8px;background-color:var(--ngx-editor-toolbar-bg);border-bottom:1px solid var(--ngx-editor-border);-webkit-user-select:none;user-select:none;z-index:10}.ngx-editor-toolbar-group{display:flex;align-items:center;gap:2px}.ngx-editor-toolbar-group:not(:last-child):after{content:\"\";display:inline-block;width:1px;height:18px;background-color:var(--ngx-editor-border);margin:0 4px}.ngx-editor-btn{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;padding:0;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--ngx-editor-text);cursor:pointer;transition:background-color .15s ease,color .15s ease,border-color .15s ease;outline:none}.ngx-editor-btn:hover:not(:disabled){background-color:var(--ngx-editor-toolbar-btn-hover)}.ngx-editor-btn:active:not(:disabled),.ngx-editor-btn.is-active{background-color:var(--ngx-editor-toolbar-btn-active);color:var(--ngx-editor-toolbar-btn-active-color);border-color:#bfdbfe}.ngx-editor-btn:disabled{opacity:.35;cursor:not-allowed}.ngx-editor-btn svg{display:block;pointer-events:none}.ngx-editor-select{height:32px;padding:0 8px;font-size:13px;color:var(--ngx-editor-text);background-color:var(--ngx-editor-bg);border:1px solid var(--ngx-editor-border);border-radius:4px;outline:none;cursor:pointer;transition:border-color .15s ease}.ngx-editor-select:hover{border-color:#cbd5e1}.ngx-editor-select:focus{border-color:var(--ngx-editor-border-focus)}.ngx-editor-color-btn{position:relative}.ngx-editor-color-btn .color-bar{position:absolute;bottom:4px;left:6px;right:6px;height:3px;border-radius:1px}.ngx-color-popover{position:absolute;top:100%;left:0;margin-top:4px;background:#fff;border:1px solid var(--ngx-editor-border);border-radius:6px;box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;padding:8px;z-index:50;width:176px}.ngx-color-popover .color-grid{display:grid;grid-template-columns:repeat(6,1fr);gap:4px;margin-bottom:8px}.ngx-color-popover .color-swatch{width:22px;height:22px;border-radius:3px;border:1px solid rgba(0,0,0,.15);cursor:pointer;padding:0;transition:transform .1s ease}.ngx-color-popover .color-swatch:hover{transform:scale(1.15);z-index:2}.ngx-color-popover .custom-color-picker{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--ngx-editor-muted)}.ngx-color-popover .custom-color-picker input[type=color]{width:28px;height:24px;border:1px solid var(--ngx-editor-border);border-radius:3px;padding:0;cursor:pointer;background:none}.ngx-editor-content-wrapper{position:relative;flex:1;display:flex;flex-direction:column}.ngx-editor-content{flex:1;padding:16px;outline:none;font-family:var(--ngx-editor-font-family, inherit);font-size:var(--ngx-editor-font-size, 15px);line-height:var(--ngx-editor-line-height, 1.65);color:var(--ngx-editor-text);overflow-y:auto;min-height:200px;word-break:break-word}.ngx-editor-content p{margin:0;min-height:1.65em}.ngx-editor-content b,.ngx-editor-content strong{font-weight:700!important}.ngx-editor-content i,.ngx-editor-content em{font-style:italic!important}.ngx-editor-content u,.ngx-editor-content .publish-editor-underline{text-decoration:underline!important;text-decoration-thickness:1px;text-underline-offset:.12em}.ngx-editor-content h1{font-size:2em;font-weight:700;margin:.67em 0;line-height:1.25}.ngx-editor-content h2{font-size:1.6em;font-weight:600;margin:.75em 0;line-height:1.3}.ngx-editor-content h3{font-size:1.3em;font-weight:600;margin:.83em 0}.ngx-editor-content h4{font-size:1.1em;font-weight:600;margin:1em 0}.ngx-editor-content ul,.ngx-editor-content ol{margin:0 0 1em 1.5em;padding:0}.ngx-editor-content li{margin-bottom:.25em}.ngx-editor-content blockquote{margin:1em 0;padding:.5em 1em;border-left:4px solid var(--ngx-editor-border-focus);background-color:#f8fafc;color:var(--ngx-editor-muted);font-style:italic}.ngx-editor-content pre{background-color:#1e293b;color:#f8fafc;padding:12px;border-radius:6px;font-family:Consolas,Monaco,Courier New,monospace;font-size:13px;overflow-x:auto;margin:1em 0}.ngx-editor-content code{background-color:#f1f5f9;color:#e11d48;padding:2px 5px;border-radius:4px;font-family:Consolas,Monaco,Courier New,monospace;font-size:.9em}.ngx-editor-content a{color:#2563eb;text-decoration:underline;text-underline-offset:2px;cursor:pointer}.ngx-editor-content .ngx-editor-hashtag,.ngx-editor-content .post-content-hashtag{color:var(--ngx-editor-hashtag-color, #0069FF)!important;font-weight:500;display:inline;cursor:text}.ngx-editor-content hr{border:none;border-top:1px solid var(--ngx-editor-border);margin:1.5em 0}.ngx-editor-content img{max-width:100%;height:auto;border-radius:4px}.ngx-editor-content[data-empty=true]:before{content:attr(data-placeholder);position:absolute;color:#94a3b8;pointer-events:none;-webkit-user-select:none;user-select:none}.ngx-editor-content:has(ol,ul,li,table,img,hr,blockquote,pre):before{display:none!important;content:none!important}.ngx-editor-source{width:100%;flex:1;min-height:200px;padding:16px;border:none;outline:none;font-family:Consolas,Monaco,Courier New,monospace;font-size:13.5px;line-height:1.5;background-color:#0f172a;color:#38bdf8;resize:vertical;box-sizing:border-box}.ngx-editor-statusbar{display:flex;align-items:center;justify-content:space-between;padding:6px 12px;background-color:var(--ngx-editor-toolbar-bg);border-top:1px solid var(--ngx-editor-border);font-size:12px;color:var(--ngx-editor-muted)}.ngx-editor-statusbar .statusbar-left,.ngx-editor-statusbar .statusbar-right{display:flex;align-items:center;gap:12px}.ngx-editor-statusbar .status-tag{background-color:#e2e8f0;padding:2px 6px;border-radius:4px;font-weight:600;text-transform:uppercase;font-size:10px}.ngx-editor-statusbar .status-badge{background-color:#fee2e2;color:#ef4444;padding:2px 6px;border-radius:4px;font-weight:500;font-size:11px}.ngx-editor-modal-backdrop{position:absolute;inset:0;background:#0f172a66;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);display:flex;align-items:center;justify-content:center;z-index:100;animation:fadeIn .15s ease-out}.ngx-editor-modal{background:#fff;border-radius:8px;box-shadow:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a;width:90%;max-width:400px;padding:20px;box-sizing:border-box}.ngx-editor-modal .modal-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px}.ngx-editor-modal .modal-header h3{margin:0;font-size:16px;font-weight:600;color:var(--ngx-editor-text)}.ngx-editor-modal .modal-body{display:flex;flex-direction:column;gap:12px}.ngx-editor-modal .form-group{display:flex;flex-direction:column;gap:4px}.ngx-editor-modal .form-group label{font-size:13px;font-weight:500;color:var(--ngx-editor-text)}.ngx-editor-modal .form-group input[type=text],.ngx-editor-modal .form-group input[type=url]{padding:8px 12px;border:1px solid var(--ngx-editor-border);border-radius:6px;font-size:14px;outline:none;transition:border-color .15s ease}.ngx-editor-modal .form-group input[type=text]:focus,.ngx-editor-modal .form-group input[type=url]:focus{border-color:var(--ngx-editor-border-focus)}.ngx-editor-modal .form-group .checkbox-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-size:13px;color:var(--ngx-editor-text);-webkit-user-select:none;user-select:none}.ngx-editor-modal .modal-footer{display:flex;justify-content:flex-end;gap:8px;margin-top:20px}.ngx-editor-modal .modal-footer button{padding:7px 14px;border-radius:6px;font-size:13px;font-weight:500;cursor:pointer;transition:all .15s ease;outline:none}.ngx-editor-modal .modal-footer .btn-secondary{background:#f1f5f9;border:1px solid var(--ngx-editor-border);color:var(--ngx-editor-text)}.ngx-editor-modal .modal-footer .btn-secondary:hover{background:#e2e8f0}.ngx-editor-modal .modal-footer .btn-primary{background:#2563eb;border:1px solid #2563eb;color:#fff}.ngx-editor-modal .modal-footer .btn-primary:hover{background:#1d4ed8}.ngx-editor-modal .modal-footer .btn-primary:disabled{opacity:.5;cursor:not-allowed}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i3.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i4.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: NgxEditorIconComponent, selector: "ngx-editor-icon", inputs: ["name"] }, { kind: "component", type: NgxEditorToolbarComponent, selector: "ngx-editor-toolbar", inputs: ["editor", "tools", "fontFamilies"], outputs: ["actionTriggered"] }] });
2706
3637
  }
2707
3638
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NgxRichTextEditorComponent, decorators: [{
2708
3639
  type: Component,
@@ -2712,7 +3643,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
2712
3643
  useExisting: forwardRef(() => NgxRichTextEditorComponent),
2713
3644
  multi: true,
2714
3645
  },
2715
- ], template: "<div\n class=\"ngx-editor-container\"\n [ngClass]=\"customClass\"\n [class.is-focused]=\"isFocused\"\n [class.is-disabled]=\"disabled\"\n [style.min-height]=\"computedConfig.minHeight\"\n [style.max-height]=\"computedConfig.maxHeight\"\n [style.font-family]=\"fontFamily || computedConfig.fontFamily || null\"\n [style.font-size]=\"fontSize || computedConfig.fontSize || null\"\n [ngStyle]=\"parsedEditorStyle\"\n>\n <!-- Toolbar -->\n @if (computedConfig.showToolbar && !readonly) {\n <!-- Array-configured Toolbar (if tools input is provided) -->\n @if (computedConfig.tools && computedConfig.tools.length > 0) {\n <ngx-editor-toolbar\n [editor]=\"this\"\n [tools]=\"computedConfig.tools\"\n [fontFamilies]=\"resolvedFontFamilies\"\n ></ngx-editor-toolbar>\n } @else {\n <!-- Default Full Toolbar -->\n <div class=\"ngx-editor-toolbar\" role=\"toolbar\" aria-label=\"Editor Toolbar\">\n <!-- History -->\n @if (computedConfig.toolbar?.history) {\n <div class=\"ngx-editor-toolbar-group\">\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Undo (Ctrl+Z)\"\n aria-label=\"Undo\"\n (mousedown)=\"$event.preventDefault(); undo()\"\n >\n <ngx-editor-icon name=\"undo\"></ngx-editor-icon>\n </button>\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Redo (Ctrl+Y)\"\n aria-label=\"Redo\"\n (mousedown)=\"$event.preventDefault(); redo()\"\n >\n <ngx-editor-icon name=\"redo\"></ngx-editor-icon>\n </button>\n </div>\n }\n\n <!-- Format Block -->\n @if (computedConfig.toolbar?.format) {\n <div class=\"ngx-editor-toolbar-group\">\n <select\n class=\"ngx-editor-select\"\n title=\"Paragraph Format\"\n aria-label=\"Paragraph Format\"\n [value]=\"activeBlockTag\"\n (change)=\"onFormatBlockChange($event)\"\n >\n <option value=\"p\">Normal text</option>\n <option value=\"h1\">Heading 1</option>\n <option value=\"h2\">Heading 2</option>\n <option value=\"h3\">Heading 3</option>\n <option value=\"h4\">Heading 4</option>\n <option value=\"blockquote\">Quote</option>\n <option value=\"pre\">Code Block</option>\n </select>\n </div>\n }\n\n <!-- Font Family -->\n @if (computedConfig.toolbar?.fontName) {\n <div class=\"ngx-editor-toolbar-group\">\n <select\n class=\"ngx-editor-select\"\n title=\"Font Family\"\n aria-label=\"Font Family\"\n (change)=\"onFontNameChange($event)\"\n >\n <option value=\"\">Font</option>\n @for (font of resolvedFontFamilies; track font.value) {\n <option [value]=\"font.value\">{{ font.label }}</option>\n }\n </select>\n </div>\n }\n\n <!-- Font Size -->\n @if (computedConfig.toolbar?.fontSize) {\n <div class=\"ngx-editor-toolbar-group\">\n <select\n class=\"ngx-editor-select\"\n title=\"Font Size\"\n aria-label=\"Font Size\"\n (change)=\"onFontSizeChange($event)\"\n >\n <option value=\"\">Size</option>\n <option value=\"1\">Small (10pt)</option>\n <option value=\"3\">Normal (12pt)</option>\n <option value=\"5\">Large (18pt)</option>\n <option value=\"7\">Huge (36pt)</option>\n </select>\n </div>\n }\n\n <!-- Inline Styles: Bold, Italic, Underline, Strike, Sub, Sup, Code -->\n @if (computedConfig.toolbar?.inline) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.inline?.bold !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.bold\"\n title=\"Bold (Ctrl+B)\"\n aria-label=\"Bold\"\n (mousedown)=\"$event.preventDefault(); bold()\"\n >\n <ngx-editor-icon name=\"bold\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.italic !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.italic\"\n title=\"Italic (Ctrl+I)\"\n aria-label=\"Italic\"\n (mousedown)=\"$event.preventDefault(); italic()\"\n >\n <ngx-editor-icon name=\"italic\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.underline !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.underline\"\n title=\"Underline (Ctrl+U)\"\n aria-label=\"Underline\"\n (mousedown)=\"$event.preventDefault(); underline()\"\n >\n <ngx-editor-icon name=\"underline\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.strikeThrough !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.strikeThrough\"\n title=\"Strikethrough\"\n aria-label=\"Strikethrough\"\n (mousedown)=\"$event.preventDefault(); strike()\"\n >\n <ngx-editor-icon name=\"strike\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.subscript !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.subscript\"\n title=\"Subscript\"\n aria-label=\"Subscript\"\n (mousedown)=\"$event.preventDefault(); subscript()\"\n >\n <ngx-editor-icon name=\"subscript\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.superscript !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.superscript\"\n title=\"Superscript\"\n aria-label=\"Superscript\"\n (mousedown)=\"$event.preventDefault(); superscript()\"\n >\n <ngx-editor-icon name=\"superscript\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.code !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Inline Code\"\n aria-label=\"Inline Code\"\n (mousedown)=\"$event.preventDefault(); wrapSelectionWithCode()\"\n >\n <ngx-editor-icon name=\"code\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n\n <!-- Colors -->\n @if (computedConfig.toolbar?.color) {\n <div class=\"ngx-editor-toolbar-group\" style=\"position: relative;\">\n @if (computedConfig.toolbar?.color?.textColor !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn ngx-editor-color-btn\"\n title=\"Text Color\"\n aria-label=\"Text Color\"\n (click)=\"toggleColorPicker('text')\"\n >\n <ngx-editor-icon name=\"textColor\"></ngx-editor-icon>\n </button>\n <div\n class=\"color-bar\"\n [style.background-color]=\"selectedTextColor\"\n ></div>\n }\n\n @if (computedConfig.toolbar?.color?.backgroundColor !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn ngx-editor-color-btn\"\n title=\"Highlight Color\"\n aria-label=\"Highlight Color\"\n (click)=\"toggleColorPicker('background')\"\n >\n <ngx-editor-icon name=\"bgColor\"></ngx-editor-icon>\n </button>\n <div\n class=\"color-bar\"\n [style.background-color]=\"selectedBgColor\"\n ></div>\n }\n\n <!-- Color Popover -->\n @if (activeColorPicker) {\n <div class=\"ngx-color-popover\" (mousedown)=\"$event.stopPropagation()\">\n <div class=\"color-grid\">\n @for (color of computedConfig.colorPalette; track color) {\n <button\n type=\"button\"\n class=\"color-swatch\"\n [style.background-color]=\"color\"\n (click)=\"applyColor(color)\"\n ></button>\n }\n </div>\n <div class=\"custom-color-picker\">\n <input\n type=\"color\"\n (change)=\"onCustomColorChange($event)\"\n />\n <span>Custom color</span>\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Alignment -->\n @if (computedConfig.toolbar?.alignment) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.alignment?.left !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.justifyLeft\"\n title=\"Align Left\"\n aria-label=\"Align Left\"\n (mousedown)=\"$event.preventDefault(); alignLeft()\"\n >\n <ngx-editor-icon name=\"alignLeft\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.alignment?.center !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.justifyCenter\"\n title=\"Align Center\"\n aria-label=\"Align Center\"\n (mousedown)=\"$event.preventDefault(); alignCenter()\"\n >\n <ngx-editor-icon name=\"alignCenter\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.alignment?.right !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.justifyRight\"\n title=\"Align Right\"\n aria-label=\"Align Right\"\n (mousedown)=\"$event.preventDefault(); alignRight()\"\n >\n <ngx-editor-icon name=\"alignRight\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.alignment?.justify !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.justifyFull\"\n title=\"Justify\"\n aria-label=\"Justify\"\n (mousedown)=\"$event.preventDefault(); alignJustify()\"\n >\n <ngx-editor-icon name=\"alignJustify\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n\n <!-- Lists & Indentation -->\n @if (computedConfig.toolbar?.list) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.list?.unordered !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.insertUnorderedList\"\n title=\"Bullet List\"\n aria-label=\"Bullet List\"\n (mousedown)=\"$event.preventDefault(); insertUnorderedList()\"\n >\n <ngx-editor-icon name=\"unorderedList\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.list?.ordered !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.insertOrderedList\"\n title=\"Numbered List\"\n aria-label=\"Numbered List\"\n (mousedown)=\"$event.preventDefault(); insertOrderedList()\"\n >\n <ngx-editor-icon name=\"orderedList\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.list?.indent !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Increase Indent\"\n aria-label=\"Increase Indent\"\n (mousedown)=\"$event.preventDefault(); indent()\"\n >\n <ngx-editor-icon name=\"indent\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.list?.outdent !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Decrease Indent\"\n aria-label=\"Decrease Indent\"\n (mousedown)=\"$event.preventDefault(); outdent()\"\n >\n <ngx-editor-icon name=\"outdent\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n\n <!-- Insert Options -->\n @if (computedConfig.toolbar?.insert) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.insert?.link !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.link\"\n title=\"Insert Link (Ctrl+K)\"\n aria-label=\"Insert Link\"\n (click)=\"openLinkModal()\"\n >\n <ngx-editor-icon name=\"link\"></ngx-editor-icon>\n </button>\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Remove Link\"\n aria-label=\"Remove Link\"\n (mousedown)=\"$event.preventDefault(); unlink()\"\n >\n <ngx-editor-icon name=\"unlink\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.insert?.image !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Insert Image\"\n aria-label=\"Insert Image\"\n (click)=\"openImageModal()\"\n >\n <ngx-editor-icon name=\"image\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.insert?.horizontalRule !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Horizontal Line\"\n aria-label=\"Horizontal Line\"\n (mousedown)=\"$event.preventDefault(); insertHorizontalRule()\"\n >\n <ngx-editor-icon name=\"horizontalRule\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n\n <!-- Extra Actions -->\n @if (computedConfig.toolbar?.extra) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.extra?.clearFormat !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Clear Formatting\"\n aria-label=\"Clear Formatting\"\n (mousedown)=\"$event.preventDefault(); clearFormatting()\"\n >\n <ngx-editor-icon name=\"clearFormat\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.extra?.sourceView !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"isSourceView\"\n title=\"View HTML Source\"\n aria-label=\"View HTML Source\"\n (click)=\"toggleSourceView()\"\n >\n <ngx-editor-icon name=\"sourceView\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n </div>\n }\n }\n\n <!-- Editor Content Area -->\n <div class=\"ngx-editor-content-wrapper\">\n <!-- Visual contenteditable -->\n <div\n #editorContent\n class=\"ngx-editor-content\"\n [style.display]=\"isSourceView ? 'none' : 'block'\"\n [style.height]=\"computedConfig.height\"\n [style.font-family]=\"fontFamily || computedConfig.fontFamily || null\"\n [style.font-size]=\"fontSize || computedConfig.fontSize || null\"\n [ngStyle]=\"parsedContentStyle\"\n [attr.contenteditable]=\"!disabled && !readonly\"\n [attr.spellcheck]=\"computedConfig.spellcheck\"\n [attr.data-placeholder]=\"computedConfig.placeholder\"\n [attr.data-empty]=\"isEmpty\"\n (input)=\"onEditorInput()\"\n (focus)=\"onEditorFocus($event)\"\n (blur)=\"onEditorBlur($event)\"\n (keydown)=\"onEditorKeydown($event)\"\n (keyup)=\"updateEditorState()\"\n (mouseup)=\"updateEditorState()\"\n (paste)=\"onEditorPaste($event)\"\n ></div>\n\n <!-- Raw HTML Source Code View -->\n @if (isSourceView) {\n <textarea\n #sourceTextarea\n class=\"ngx-editor-source\"\n [style.height]=\"computedConfig.height\"\n [disabled]=\"disabled || readonly\"\n [value]=\"content\"\n (input)=\"onSourceInput($event)\"\n ></textarea>\n }\n </div>\n\n <!-- Status Bar -->\n @if (computedConfig.showStatusBar) {\n <div class=\"ngx-editor-statusbar\">\n <div class=\"statusbar-left\">\n @if (activeBlockTag) {\n <span class=\"status-tag\">&lt;{{ activeBlockTag }}&gt;</span>\n }\n @if (isSourceView) {\n <span class=\"status-badge\">HTML SOURCE MODE</span>\n }\n </div>\n <div class=\"statusbar-right\">\n @if (computedConfig.showWordCount) {\n <span>Words: <strong>{{ stats.words }}</strong></span>\n }\n @if (computedConfig.maxCharacters) {\n <span [class.limit-reached]=\"stats.chars > computedConfig.maxCharacters\">\n {{ stats.chars }}/{{ computedConfig.maxCharacters }} Characters\n </span>\n } @else if (computedConfig.showCharCount) {\n <span>Characters: <strong>{{ stats.chars }}</strong></span>\n }\n </div>\n </div>\n }\n\n <!-- Link Modal -->\n @if (showLinkModal) {\n <div class=\"ngx-editor-modal-backdrop\" (click)=\"closeLinkModal()\">\n <div class=\"ngx-editor-modal\" (click)=\"$event.stopPropagation()\">\n <div class=\"modal-header\">\n <h3>Insert Link</h3>\n <button type=\"button\" class=\"ngx-editor-btn\" (click)=\"closeLinkModal()\" aria-label=\"Close\">\n <ngx-editor-icon name=\"close\"></ngx-editor-icon>\n </button>\n </div>\n <div class=\"modal-body\">\n <div class=\"form-group\">\n <label>Link Text</label>\n <input\n type=\"text\"\n placeholder=\"Display text\"\n [(ngModel)]=\"linkModel.text\"\n />\n </div>\n <div class=\"form-group\">\n <label>Target URL</label>\n <input\n type=\"url\"\n placeholder=\"https://example.com\"\n [(ngModel)]=\"linkModel.url\"\n (keydown.enter)=\"insertLinkFromModal()\"\n />\n </div>\n <div class=\"form-group\">\n <label class=\"checkbox-label\">\n <input type=\"checkbox\" [(ngModel)]=\"linkModel.newTab\" />\n Open in a new tab\n </label>\n </div>\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn-secondary\" (click)=\"closeLinkModal()\">Cancel</button>\n <button\n type=\"button\"\n class=\"btn-primary\"\n [disabled]=\"!linkModel.url\"\n (click)=\"insertLinkFromModal()\"\n >\n Insert Link\n </button>\n </div>\n </div>\n </div>\n }\n\n <!-- Image Modal -->\n @if (showImageModal) {\n <div class=\"ngx-editor-modal-backdrop\" (click)=\"closeImageModal()\">\n <div class=\"ngx-editor-modal\" (click)=\"$event.stopPropagation()\">\n <div class=\"modal-header\">\n <h3>Insert Image</h3>\n <button type=\"button\" class=\"ngx-editor-btn\" (click)=\"closeImageModal()\" aria-label=\"Close\">\n <ngx-editor-icon name=\"close\"></ngx-editor-icon>\n </button>\n </div>\n <div class=\"modal-body\">\n <div class=\"form-group\">\n <label>Image URL</label>\n <input\n type=\"url\"\n placeholder=\"https://example.com/image.png\"\n [(ngModel)]=\"imageModel.url\"\n />\n </div>\n <div class=\"form-group\">\n <label>Or Upload File</label>\n <input\n type=\"file\"\n accept=\"image/*\"\n (change)=\"onImageFileUpload($event)\"\n />\n </div>\n <div class=\"form-group\">\n <label>Alt Description</label>\n <input\n type=\"text\"\n placeholder=\"Image description\"\n [(ngModel)]=\"imageModel.alt\"\n />\n </div>\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn-secondary\" (click)=\"closeImageModal()\">Cancel</button>\n <button\n type=\"button\"\n class=\"btn-primary\"\n [disabled]=\"!imageModel.url\"\n (click)=\"insertImageFromModal()\"\n >\n Insert Image\n </button>\n </div>\n </div>\n </div>\n }\n</div>\n", styles: [":host{display:block;font-family:var(--ngx-editor-font-family, inherit);box-sizing:border-box;--ngx-editor-font-family: inherit;--ngx-editor-font-size: 15px;--ngx-editor-line-height: 1.65;--ngx-editor-border: #e2e8f0;--ngx-editor-border-focus: #3b82f6;--ngx-editor-bg: #ffffff;--ngx-editor-toolbar-bg: #f8fafc;--ngx-editor-toolbar-btn-hover: #e2e8f0;--ngx-editor-toolbar-btn-active: #dbeafe;--ngx-editor-toolbar-btn-active-color: #1d4ed8;--ngx-editor-text: #1e293b;--ngx-editor-muted: #64748b;--ngx-editor-radius: 8px;--ngx-editor-shadow: 0 1px 3px 0 rgba(0, 0, 0, .1), 0 1px 2px -1px rgba(0, 0, 0, .1);--ngx-editor-shadow-focus: 0 0 0 3px rgba(59, 130, 246, .25);--ngx-editor-hashtag-color: #0069FF}.ngx-editor-container{display:flex;flex-direction:column;background-color:var(--ngx-editor-bg);border:1px solid var(--ngx-editor-border);border-radius:var(--ngx-editor-radius);box-shadow:var(--ngx-editor-shadow);overflow:hidden;transition:border-color .2s ease,box-shadow .2s ease;position:relative}.ngx-editor-container.is-focused{border-color:var(--ngx-editor-border-focus);box-shadow:var(--ngx-editor-shadow-focus)}.ngx-editor-container.is-disabled{background-color:#f1f5f9;opacity:.75;pointer-events:none;-webkit-user-select:none;user-select:none}.ngx-editor-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:4px;padding:6px 8px;background-color:var(--ngx-editor-toolbar-bg);border-bottom:1px solid var(--ngx-editor-border);-webkit-user-select:none;user-select:none;z-index:10}.ngx-editor-toolbar-group{display:flex;align-items:center;gap:2px}.ngx-editor-toolbar-group:not(:last-child):after{content:\"\";display:inline-block;width:1px;height:18px;background-color:var(--ngx-editor-border);margin:0 4px}.ngx-editor-btn{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;padding:0;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--ngx-editor-text);cursor:pointer;transition:background-color .15s ease,color .15s ease,border-color .15s ease;outline:none}.ngx-editor-btn:hover:not(:disabled){background-color:var(--ngx-editor-toolbar-btn-hover)}.ngx-editor-btn:active:not(:disabled),.ngx-editor-btn.is-active{background-color:var(--ngx-editor-toolbar-btn-active);color:var(--ngx-editor-toolbar-btn-active-color);border-color:#bfdbfe}.ngx-editor-btn:disabled{opacity:.35;cursor:not-allowed}.ngx-editor-btn svg{display:block;pointer-events:none}.ngx-editor-select{height:32px;padding:0 8px;font-size:13px;color:var(--ngx-editor-text);background-color:var(--ngx-editor-bg);border:1px solid var(--ngx-editor-border);border-radius:4px;outline:none;cursor:pointer;transition:border-color .15s ease}.ngx-editor-select:hover{border-color:#cbd5e1}.ngx-editor-select:focus{border-color:var(--ngx-editor-border-focus)}.ngx-editor-color-btn{position:relative}.ngx-editor-color-btn .color-bar{position:absolute;bottom:4px;left:6px;right:6px;height:3px;border-radius:1px}.ngx-color-popover{position:absolute;top:100%;left:0;margin-top:4px;background:#fff;border:1px solid var(--ngx-editor-border);border-radius:6px;box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;padding:8px;z-index:50;width:176px}.ngx-color-popover .color-grid{display:grid;grid-template-columns:repeat(6,1fr);gap:4px;margin-bottom:8px}.ngx-color-popover .color-swatch{width:22px;height:22px;border-radius:3px;border:1px solid rgba(0,0,0,.15);cursor:pointer;padding:0;transition:transform .1s ease}.ngx-color-popover .color-swatch:hover{transform:scale(1.15);z-index:2}.ngx-color-popover .custom-color-picker{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--ngx-editor-muted)}.ngx-color-popover .custom-color-picker input[type=color]{width:28px;height:24px;border:1px solid var(--ngx-editor-border);border-radius:3px;padding:0;cursor:pointer;background:none}.ngx-editor-content-wrapper{position:relative;flex:1;display:flex;flex-direction:column}.ngx-editor-content{flex:1;padding:16px;outline:none;font-family:var(--ngx-editor-font-family, inherit);font-size:var(--ngx-editor-font-size, 15px);line-height:var(--ngx-editor-line-height, 1.65);color:var(--ngx-editor-text);overflow-y:auto;min-height:200px;word-break:break-word}.ngx-editor-content p{margin:0 0 1em}.ngx-editor-content b,.ngx-editor-content strong{font-weight:700!important}.ngx-editor-content i,.ngx-editor-content em{font-style:italic!important}.ngx-editor-content u,.ngx-editor-content .publish-editor-underline{text-decoration:underline!important;text-decoration-thickness:1px;text-underline-offset:.12em}.ngx-editor-content h1{font-size:2em;font-weight:700;margin:.67em 0;line-height:1.25}.ngx-editor-content h2{font-size:1.6em;font-weight:600;margin:.75em 0;line-height:1.3}.ngx-editor-content h3{font-size:1.3em;font-weight:600;margin:.83em 0}.ngx-editor-content h4{font-size:1.1em;font-weight:600;margin:1em 0}.ngx-editor-content ul,.ngx-editor-content ol{margin:0 0 1em 1.5em;padding:0}.ngx-editor-content li{margin-bottom:.25em}.ngx-editor-content blockquote{margin:1em 0;padding:.5em 1em;border-left:4px solid var(--ngx-editor-border-focus);background-color:#f8fafc;color:var(--ngx-editor-muted);font-style:italic}.ngx-editor-content pre{background-color:#1e293b;color:#f8fafc;padding:12px;border-radius:6px;font-family:Consolas,Monaco,Courier New,monospace;font-size:13px;overflow-x:auto;margin:1em 0}.ngx-editor-content code{background-color:#f1f5f9;color:#e11d48;padding:2px 5px;border-radius:4px;font-family:Consolas,Monaco,Courier New,monospace;font-size:.9em}.ngx-editor-content a{color:#2563eb;text-decoration:underline;text-underline-offset:2px;cursor:pointer}.ngx-editor-content .ngx-editor-hashtag,.ngx-editor-content .post-content-hashtag{color:var(--ngx-editor-hashtag-color, #0069FF)!important;font-weight:500;display:inline;cursor:text}.ngx-editor-content hr{border:none;border-top:1px solid var(--ngx-editor-border);margin:1.5em 0}.ngx-editor-content img{max-width:100%;height:auto;border-radius:4px}.ngx-editor-content[data-empty=true]:before{content:attr(data-placeholder);position:absolute;color:#94a3b8;pointer-events:none;-webkit-user-select:none;user-select:none}.ngx-editor-content:has(ol,ul,li,table,img,hr,blockquote,pre):before{display:none!important;content:none!important}.ngx-editor-source{width:100%;flex:1;min-height:200px;padding:16px;border:none;outline:none;font-family:Consolas,Monaco,Courier New,monospace;font-size:13.5px;line-height:1.5;background-color:#0f172a;color:#38bdf8;resize:vertical;box-sizing:border-box}.ngx-editor-statusbar{display:flex;align-items:center;justify-content:space-between;padding:6px 12px;background-color:var(--ngx-editor-toolbar-bg);border-top:1px solid var(--ngx-editor-border);font-size:12px;color:var(--ngx-editor-muted)}.ngx-editor-statusbar .statusbar-left,.ngx-editor-statusbar .statusbar-right{display:flex;align-items:center;gap:12px}.ngx-editor-statusbar .status-tag{background-color:#e2e8f0;padding:2px 6px;border-radius:4px;font-weight:600;text-transform:uppercase;font-size:10px}.ngx-editor-statusbar .status-badge{background-color:#fee2e2;color:#ef4444;padding:2px 6px;border-radius:4px;font-weight:500;font-size:11px}.ngx-editor-modal-backdrop{position:absolute;inset:0;background:#0f172a66;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);display:flex;align-items:center;justify-content:center;z-index:100;animation:fadeIn .15s ease-out}.ngx-editor-modal{background:#fff;border-radius:8px;box-shadow:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a;width:90%;max-width:400px;padding:20px;box-sizing:border-box}.ngx-editor-modal .modal-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px}.ngx-editor-modal .modal-header h3{margin:0;font-size:16px;font-weight:600;color:var(--ngx-editor-text)}.ngx-editor-modal .modal-body{display:flex;flex-direction:column;gap:12px}.ngx-editor-modal .form-group{display:flex;flex-direction:column;gap:4px}.ngx-editor-modal .form-group label{font-size:13px;font-weight:500;color:var(--ngx-editor-text)}.ngx-editor-modal .form-group input[type=text],.ngx-editor-modal .form-group input[type=url]{padding:8px 12px;border:1px solid var(--ngx-editor-border);border-radius:6px;font-size:14px;outline:none;transition:border-color .15s ease}.ngx-editor-modal .form-group input[type=text]:focus,.ngx-editor-modal .form-group input[type=url]:focus{border-color:var(--ngx-editor-border-focus)}.ngx-editor-modal .form-group .checkbox-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-size:13px;color:var(--ngx-editor-text);-webkit-user-select:none;user-select:none}.ngx-editor-modal .modal-footer{display:flex;justify-content:flex-end;gap:8px;margin-top:20px}.ngx-editor-modal .modal-footer button{padding:7px 14px;border-radius:6px;font-size:13px;font-weight:500;cursor:pointer;transition:all .15s ease;outline:none}.ngx-editor-modal .modal-footer .btn-secondary{background:#f1f5f9;border:1px solid var(--ngx-editor-border);color:var(--ngx-editor-text)}.ngx-editor-modal .modal-footer .btn-secondary:hover{background:#e2e8f0}.ngx-editor-modal .modal-footer .btn-primary{background:#2563eb;border:1px solid #2563eb;color:#fff}.ngx-editor-modal .modal-footer .btn-primary:hover{background:#1d4ed8}.ngx-editor-modal .modal-footer .btn-primary:disabled{opacity:.5;cursor:not-allowed}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}\n"] }]
3646
+ ], template: "<div\n class=\"ngx-editor-container\"\n [ngClass]=\"customClass\"\n [class.is-focused]=\"isFocused\"\n [class.is-disabled]=\"disabled\"\n [style.min-height]=\"computedConfig.minHeight\"\n [style.max-height]=\"computedConfig.maxHeight\"\n [style.font-family]=\"fontFamily || computedConfig.fontFamily || null\"\n [style.font-size]=\"fontSize || computedConfig.fontSize || null\"\n [ngStyle]=\"parsedEditorStyle\"\n>\n <!-- Toolbar -->\n @if (computedConfig.showToolbar && !readonly) {\n <!-- Array-configured Toolbar (if tools input is provided) -->\n @if (computedConfig.tools && computedConfig.tools.length > 0) {\n <ngx-editor-toolbar\n [editor]=\"this\"\n [tools]=\"computedConfig.tools\"\n [fontFamilies]=\"resolvedFontFamilies\"\n ></ngx-editor-toolbar>\n } @else {\n <!-- Default Full Toolbar -->\n <div class=\"ngx-editor-toolbar\" role=\"toolbar\" aria-label=\"Editor Toolbar\">\n <!-- History -->\n @if (computedConfig.toolbar?.history) {\n <div class=\"ngx-editor-toolbar-group\">\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Undo (Ctrl+Z)\"\n aria-label=\"Undo\"\n (mousedown)=\"$event.preventDefault(); undo()\"\n >\n <ngx-editor-icon name=\"undo\"></ngx-editor-icon>\n </button>\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Redo (Ctrl+Y)\"\n aria-label=\"Redo\"\n (mousedown)=\"$event.preventDefault(); redo()\"\n >\n <ngx-editor-icon name=\"redo\"></ngx-editor-icon>\n </button>\n </div>\n }\n\n <!-- Format Block -->\n @if (computedConfig.toolbar?.format) {\n <div class=\"ngx-editor-toolbar-group\">\n <select\n class=\"ngx-editor-select\"\n title=\"Paragraph Format\"\n aria-label=\"Paragraph Format\"\n [value]=\"activeBlockTag\"\n (change)=\"onFormatBlockChange($event)\"\n >\n <option value=\"p\">Normal text</option>\n <option value=\"h1\">Heading 1</option>\n <option value=\"h2\">Heading 2</option>\n <option value=\"h3\">Heading 3</option>\n <option value=\"h4\">Heading 4</option>\n <option value=\"blockquote\">Quote</option>\n <option value=\"pre\">Code Block</option>\n </select>\n </div>\n }\n\n <!-- Font Family -->\n @if (computedConfig.toolbar?.fontName) {\n <div class=\"ngx-editor-toolbar-group\">\n <select\n class=\"ngx-editor-select\"\n title=\"Font Family\"\n aria-label=\"Font Family\"\n (change)=\"onFontNameChange($event)\"\n >\n <option value=\"\">Font</option>\n @for (font of resolvedFontFamilies; track font.value) {\n <option [value]=\"font.value\">{{ font.label }}</option>\n }\n </select>\n </div>\n }\n\n <!-- Font Size -->\n @if (computedConfig.toolbar?.fontSize) {\n <div class=\"ngx-editor-toolbar-group\">\n <select\n class=\"ngx-editor-select\"\n title=\"Font Size\"\n aria-label=\"Font Size\"\n (change)=\"onFontSizeChange($event)\"\n >\n <option value=\"\">Size</option>\n <option value=\"1\">Small (10pt)</option>\n <option value=\"3\">Normal (12pt)</option>\n <option value=\"5\">Large (18pt)</option>\n <option value=\"7\">Huge (36pt)</option>\n </select>\n </div>\n }\n\n <!-- Inline Styles: Bold, Italic, Underline, Strike, Sub, Sup, Code -->\n @if (computedConfig.toolbar?.inline) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.inline?.bold !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.bold\"\n title=\"Bold (Ctrl+B)\"\n aria-label=\"Bold\"\n (mousedown)=\"$event.preventDefault(); bold()\"\n >\n <ngx-editor-icon name=\"bold\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.italic !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.italic\"\n title=\"Italic (Ctrl+I)\"\n aria-label=\"Italic\"\n (mousedown)=\"$event.preventDefault(); italic()\"\n >\n <ngx-editor-icon name=\"italic\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.underline !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.underline\"\n title=\"Underline (Ctrl+U)\"\n aria-label=\"Underline\"\n (mousedown)=\"$event.preventDefault(); underline()\"\n >\n <ngx-editor-icon name=\"underline\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.strikeThrough !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.strikeThrough\"\n title=\"Strikethrough\"\n aria-label=\"Strikethrough\"\n (mousedown)=\"$event.preventDefault(); strike()\"\n >\n <ngx-editor-icon name=\"strike\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.subscript !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.subscript\"\n title=\"Subscript\"\n aria-label=\"Subscript\"\n (mousedown)=\"$event.preventDefault(); subscript()\"\n >\n <ngx-editor-icon name=\"subscript\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.superscript !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.superscript\"\n title=\"Superscript\"\n aria-label=\"Superscript\"\n (mousedown)=\"$event.preventDefault(); superscript()\"\n >\n <ngx-editor-icon name=\"superscript\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.code !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Inline Code\"\n aria-label=\"Inline Code\"\n (mousedown)=\"$event.preventDefault(); wrapSelectionWithCode()\"\n >\n <ngx-editor-icon name=\"code\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n\n <!-- Colors -->\n @if (computedConfig.toolbar?.color) {\n <div class=\"ngx-editor-toolbar-group\" style=\"position: relative;\">\n @if (computedConfig.toolbar?.color?.textColor !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn ngx-editor-color-btn\"\n title=\"Text Color\"\n aria-label=\"Text Color\"\n (click)=\"toggleColorPicker('text')\"\n >\n <ngx-editor-icon name=\"textColor\"></ngx-editor-icon>\n </button>\n <div\n class=\"color-bar\"\n [style.background-color]=\"selectedTextColor\"\n ></div>\n }\n\n @if (computedConfig.toolbar?.color?.backgroundColor !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn ngx-editor-color-btn\"\n title=\"Highlight Color\"\n aria-label=\"Highlight Color\"\n (click)=\"toggleColorPicker('background')\"\n >\n <ngx-editor-icon name=\"bgColor\"></ngx-editor-icon>\n </button>\n <div\n class=\"color-bar\"\n [style.background-color]=\"selectedBgColor\"\n ></div>\n }\n\n <!-- Color Popover -->\n @if (activeColorPicker) {\n <div class=\"ngx-color-popover\" (mousedown)=\"$event.stopPropagation()\">\n <div class=\"color-grid\">\n @for (color of computedConfig.colorPalette; track color) {\n <button\n type=\"button\"\n class=\"color-swatch\"\n [style.background-color]=\"color\"\n (click)=\"applyColor(color)\"\n ></button>\n }\n </div>\n <div class=\"custom-color-picker\">\n <input\n type=\"color\"\n (change)=\"onCustomColorChange($event)\"\n />\n <span>Custom color</span>\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Alignment -->\n @if (computedConfig.toolbar?.alignment) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.alignment?.left !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.justifyLeft\"\n title=\"Align Left\"\n aria-label=\"Align Left\"\n (mousedown)=\"$event.preventDefault(); alignLeft()\"\n >\n <ngx-editor-icon name=\"alignLeft\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.alignment?.center !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.justifyCenter\"\n title=\"Align Center\"\n aria-label=\"Align Center\"\n (mousedown)=\"$event.preventDefault(); alignCenter()\"\n >\n <ngx-editor-icon name=\"alignCenter\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.alignment?.right !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.justifyRight\"\n title=\"Align Right\"\n aria-label=\"Align Right\"\n (mousedown)=\"$event.preventDefault(); alignRight()\"\n >\n <ngx-editor-icon name=\"alignRight\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.alignment?.justify !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.justifyFull\"\n title=\"Justify\"\n aria-label=\"Justify\"\n (mousedown)=\"$event.preventDefault(); alignJustify()\"\n >\n <ngx-editor-icon name=\"alignJustify\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n\n <!-- Lists & Indentation -->\n @if (computedConfig.toolbar?.list) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.list?.unordered !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.insertUnorderedList\"\n title=\"Bullet List\"\n aria-label=\"Bullet List\"\n (mousedown)=\"$event.preventDefault(); insertUnorderedList()\"\n >\n <ngx-editor-icon name=\"unorderedList\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.list?.ordered !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.insertOrderedList\"\n title=\"Numbered List\"\n aria-label=\"Numbered List\"\n (mousedown)=\"$event.preventDefault(); insertOrderedList()\"\n >\n <ngx-editor-icon name=\"orderedList\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.list?.indent !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Increase Indent\"\n aria-label=\"Increase Indent\"\n (mousedown)=\"$event.preventDefault(); indent()\"\n >\n <ngx-editor-icon name=\"indent\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.list?.outdent !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Decrease Indent\"\n aria-label=\"Decrease Indent\"\n (mousedown)=\"$event.preventDefault(); outdent()\"\n >\n <ngx-editor-icon name=\"outdent\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n\n <!-- Insert Options -->\n @if (computedConfig.toolbar?.insert) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.insert?.link !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.link\"\n title=\"Insert Link (Ctrl+K)\"\n aria-label=\"Insert Link\"\n (click)=\"openLinkModal()\"\n >\n <ngx-editor-icon name=\"link\"></ngx-editor-icon>\n </button>\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Remove Link\"\n aria-label=\"Remove Link\"\n (mousedown)=\"$event.preventDefault(); unlink()\"\n >\n <ngx-editor-icon name=\"unlink\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.insert?.image !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Insert Image\"\n aria-label=\"Insert Image\"\n (click)=\"openImageModal()\"\n >\n <ngx-editor-icon name=\"image\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.insert?.horizontalRule !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Horizontal Line\"\n aria-label=\"Horizontal Line\"\n (mousedown)=\"$event.preventDefault(); insertHorizontalRule()\"\n >\n <ngx-editor-icon name=\"horizontalRule\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n\n <!-- Extra Actions -->\n @if (computedConfig.toolbar?.extra) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.extra?.clearFormat !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Clear Formatting\"\n aria-label=\"Clear Formatting\"\n (mousedown)=\"$event.preventDefault(); clearFormatting()\"\n >\n <ngx-editor-icon name=\"clearFormat\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.extra?.sourceView !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"isSourceView\"\n title=\"View HTML Source\"\n aria-label=\"View HTML Source\"\n (click)=\"toggleSourceView()\"\n >\n <ngx-editor-icon name=\"sourceView\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n </div>\n }\n }\n\n <!-- Editor Content Area -->\n <div class=\"ngx-editor-content-wrapper\">\n <!-- Visual contenteditable -->\n <div\n #editorContent\n class=\"ngx-editor-content\"\n [style.display]=\"isSourceView ? 'none' : 'block'\"\n [style.height]=\"computedConfig.height\"\n [style.font-family]=\"fontFamily || computedConfig.fontFamily || null\"\n [style.font-size]=\"fontSize || computedConfig.fontSize || null\"\n [ngStyle]=\"parsedContentStyle\"\n [attr.contenteditable]=\"!disabled && !readonly\"\n [attr.spellcheck]=\"computedConfig.spellcheck\"\n [attr.data-placeholder]=\"computedConfig.placeholder\"\n [attr.data-empty]=\"isEmpty\"\n (input)=\"onEditorInput()\"\n (focus)=\"onEditorFocus($event)\"\n (blur)=\"onEditorBlur($event)\"\n (keydown)=\"onEditorKeydown($event)\"\n (keyup)=\"updateEditorState()\"\n (mouseup)=\"updateEditorState()\"\n (paste)=\"onEditorPaste($event)\"\n ></div>\n\n <!-- Raw HTML Source Code View -->\n @if (isSourceView) {\n <textarea\n #sourceTextarea\n class=\"ngx-editor-source\"\n [style.height]=\"computedConfig.height\"\n [disabled]=\"disabled || readonly\"\n [value]=\"content\"\n (input)=\"onSourceInput($event)\"\n ></textarea>\n }\n </div>\n\n <!-- Status Bar -->\n @if (computedConfig.showStatusBar) {\n <div class=\"ngx-editor-statusbar\">\n <div class=\"statusbar-left\">\n @if (activeBlockTag) {\n <span class=\"status-tag\">&lt;{{ activeBlockTag }}&gt;</span>\n }\n @if (isSourceView) {\n <span class=\"status-badge\">HTML SOURCE MODE</span>\n }\n </div>\n <div class=\"statusbar-right\">\n @if (computedConfig.showWordCount) {\n <span>Words: <strong>{{ stats.words }}</strong></span>\n }\n @if (computedConfig.maxCharacters) {\n <span [class.limit-reached]=\"stats.chars > computedConfig.maxCharacters\">\n {{ stats.chars }}/{{ computedConfig.maxCharacters }} Characters\n </span>\n } @else if (computedConfig.showCharCount) {\n <span>Characters: <strong>{{ stats.chars }}</strong></span>\n }\n </div>\n </div>\n }\n\n <!-- Link Modal -->\n @if (showLinkModal) {\n <div class=\"ngx-editor-modal-backdrop\" (click)=\"closeLinkModal()\">\n <div class=\"ngx-editor-modal\" (click)=\"$event.stopPropagation()\">\n <div class=\"modal-header\">\n <h3>Insert Link</h3>\n <button type=\"button\" class=\"ngx-editor-btn\" (click)=\"closeLinkModal()\" aria-label=\"Close\">\n <ngx-editor-icon name=\"close\"></ngx-editor-icon>\n </button>\n </div>\n <div class=\"modal-body\">\n <div class=\"form-group\">\n <label>Link Text</label>\n <input\n type=\"text\"\n placeholder=\"Display text\"\n [(ngModel)]=\"linkModel.text\"\n />\n </div>\n <div class=\"form-group\">\n <label>Target URL</label>\n <input\n type=\"url\"\n placeholder=\"https://example.com\"\n [(ngModel)]=\"linkModel.url\"\n (keydown.enter)=\"insertLinkFromModal()\"\n />\n </div>\n <div class=\"form-group\">\n <label class=\"checkbox-label\">\n <input type=\"checkbox\" [(ngModel)]=\"linkModel.newTab\" />\n Open in a new tab\n </label>\n </div>\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn-secondary\" (click)=\"closeLinkModal()\">Cancel</button>\n <button\n type=\"button\"\n class=\"btn-primary\"\n [disabled]=\"!linkModel.url\"\n (click)=\"insertLinkFromModal()\"\n >\n Insert Link\n </button>\n </div>\n </div>\n </div>\n }\n\n <!-- Image Modal -->\n @if (showImageModal) {\n <div class=\"ngx-editor-modal-backdrop\" (click)=\"closeImageModal()\">\n <div class=\"ngx-editor-modal\" (click)=\"$event.stopPropagation()\">\n <div class=\"modal-header\">\n <h3>Insert Image</h3>\n <button type=\"button\" class=\"ngx-editor-btn\" (click)=\"closeImageModal()\" aria-label=\"Close\">\n <ngx-editor-icon name=\"close\"></ngx-editor-icon>\n </button>\n </div>\n <div class=\"modal-body\">\n <div class=\"form-group\">\n <label>Image URL</label>\n <input\n type=\"url\"\n placeholder=\"https://example.com/image.png\"\n [(ngModel)]=\"imageModel.url\"\n />\n </div>\n <div class=\"form-group\">\n <label>Or Upload File</label>\n <input\n type=\"file\"\n accept=\"image/*\"\n (change)=\"onImageFileUpload($event)\"\n />\n </div>\n <div class=\"form-group\">\n <label>Alt Description</label>\n <input\n type=\"text\"\n placeholder=\"Image description\"\n [(ngModel)]=\"imageModel.alt\"\n />\n </div>\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn-secondary\" (click)=\"closeImageModal()\">Cancel</button>\n <button\n type=\"button\"\n class=\"btn-primary\"\n [disabled]=\"!imageModel.url\"\n (click)=\"insertImageFromModal()\"\n >\n Insert Image\n </button>\n </div>\n </div>\n </div>\n }\n</div>\n", styles: [":host{display:block;font-family:var(--ngx-editor-font-family, inherit);box-sizing:border-box;--ngx-editor-font-family: inherit;--ngx-editor-font-size: 15px;--ngx-editor-line-height: 1.65;--ngx-editor-border: #e2e8f0;--ngx-editor-border-focus: #3b82f6;--ngx-editor-bg: #ffffff;--ngx-editor-toolbar-bg: #f8fafc;--ngx-editor-toolbar-btn-hover: #e2e8f0;--ngx-editor-toolbar-btn-active: #dbeafe;--ngx-editor-toolbar-btn-active-color: #1d4ed8;--ngx-editor-text: #1e293b;--ngx-editor-muted: #64748b;--ngx-editor-radius: 8px;--ngx-editor-shadow: 0 1px 3px 0 rgba(0, 0, 0, .1), 0 1px 2px -1px rgba(0, 0, 0, .1);--ngx-editor-shadow-focus: 0 0 0 3px rgba(59, 130, 246, .25);--ngx-editor-hashtag-color: #0069FF}.ngx-editor-container{display:flex;flex-direction:column;background-color:var(--ngx-editor-bg);border:1px solid var(--ngx-editor-border);border-radius:var(--ngx-editor-radius);box-shadow:var(--ngx-editor-shadow);overflow:hidden;transition:border-color .2s ease,box-shadow .2s ease;position:relative}.ngx-editor-container.is-focused{border-color:var(--ngx-editor-border-focus);box-shadow:var(--ngx-editor-shadow-focus)}.ngx-editor-container.is-disabled{background-color:#f1f5f9;opacity:.75;pointer-events:none;-webkit-user-select:none;user-select:none}.ngx-editor-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:4px;padding:6px 8px;background-color:var(--ngx-editor-toolbar-bg);border-bottom:1px solid var(--ngx-editor-border);-webkit-user-select:none;user-select:none;z-index:10}.ngx-editor-toolbar-group{display:flex;align-items:center;gap:2px}.ngx-editor-toolbar-group:not(:last-child):after{content:\"\";display:inline-block;width:1px;height:18px;background-color:var(--ngx-editor-border);margin:0 4px}.ngx-editor-btn{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;padding:0;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--ngx-editor-text);cursor:pointer;transition:background-color .15s ease,color .15s ease,border-color .15s ease;outline:none}.ngx-editor-btn:hover:not(:disabled){background-color:var(--ngx-editor-toolbar-btn-hover)}.ngx-editor-btn:active:not(:disabled),.ngx-editor-btn.is-active{background-color:var(--ngx-editor-toolbar-btn-active);color:var(--ngx-editor-toolbar-btn-active-color);border-color:#bfdbfe}.ngx-editor-btn:disabled{opacity:.35;cursor:not-allowed}.ngx-editor-btn svg{display:block;pointer-events:none}.ngx-editor-select{height:32px;padding:0 8px;font-size:13px;color:var(--ngx-editor-text);background-color:var(--ngx-editor-bg);border:1px solid var(--ngx-editor-border);border-radius:4px;outline:none;cursor:pointer;transition:border-color .15s ease}.ngx-editor-select:hover{border-color:#cbd5e1}.ngx-editor-select:focus{border-color:var(--ngx-editor-border-focus)}.ngx-editor-color-btn{position:relative}.ngx-editor-color-btn .color-bar{position:absolute;bottom:4px;left:6px;right:6px;height:3px;border-radius:1px}.ngx-color-popover{position:absolute;top:100%;left:0;margin-top:4px;background:#fff;border:1px solid var(--ngx-editor-border);border-radius:6px;box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;padding:8px;z-index:50;width:176px}.ngx-color-popover .color-grid{display:grid;grid-template-columns:repeat(6,1fr);gap:4px;margin-bottom:8px}.ngx-color-popover .color-swatch{width:22px;height:22px;border-radius:3px;border:1px solid rgba(0,0,0,.15);cursor:pointer;padding:0;transition:transform .1s ease}.ngx-color-popover .color-swatch:hover{transform:scale(1.15);z-index:2}.ngx-color-popover .custom-color-picker{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--ngx-editor-muted)}.ngx-color-popover .custom-color-picker input[type=color]{width:28px;height:24px;border:1px solid var(--ngx-editor-border);border-radius:3px;padding:0;cursor:pointer;background:none}.ngx-editor-content-wrapper{position:relative;flex:1;display:flex;flex-direction:column}.ngx-editor-content{flex:1;padding:16px;outline:none;font-family:var(--ngx-editor-font-family, inherit);font-size:var(--ngx-editor-font-size, 15px);line-height:var(--ngx-editor-line-height, 1.65);color:var(--ngx-editor-text);overflow-y:auto;min-height:200px;word-break:break-word}.ngx-editor-content p{margin:0;min-height:1.65em}.ngx-editor-content b,.ngx-editor-content strong{font-weight:700!important}.ngx-editor-content i,.ngx-editor-content em{font-style:italic!important}.ngx-editor-content u,.ngx-editor-content .publish-editor-underline{text-decoration:underline!important;text-decoration-thickness:1px;text-underline-offset:.12em}.ngx-editor-content h1{font-size:2em;font-weight:700;margin:.67em 0;line-height:1.25}.ngx-editor-content h2{font-size:1.6em;font-weight:600;margin:.75em 0;line-height:1.3}.ngx-editor-content h3{font-size:1.3em;font-weight:600;margin:.83em 0}.ngx-editor-content h4{font-size:1.1em;font-weight:600;margin:1em 0}.ngx-editor-content ul,.ngx-editor-content ol{margin:0 0 1em 1.5em;padding:0}.ngx-editor-content li{margin-bottom:.25em}.ngx-editor-content blockquote{margin:1em 0;padding:.5em 1em;border-left:4px solid var(--ngx-editor-border-focus);background-color:#f8fafc;color:var(--ngx-editor-muted);font-style:italic}.ngx-editor-content pre{background-color:#1e293b;color:#f8fafc;padding:12px;border-radius:6px;font-family:Consolas,Monaco,Courier New,monospace;font-size:13px;overflow-x:auto;margin:1em 0}.ngx-editor-content code{background-color:#f1f5f9;color:#e11d48;padding:2px 5px;border-radius:4px;font-family:Consolas,Monaco,Courier New,monospace;font-size:.9em}.ngx-editor-content a{color:#2563eb;text-decoration:underline;text-underline-offset:2px;cursor:pointer}.ngx-editor-content .ngx-editor-hashtag,.ngx-editor-content .post-content-hashtag{color:var(--ngx-editor-hashtag-color, #0069FF)!important;font-weight:500;display:inline;cursor:text}.ngx-editor-content hr{border:none;border-top:1px solid var(--ngx-editor-border);margin:1.5em 0}.ngx-editor-content img{max-width:100%;height:auto;border-radius:4px}.ngx-editor-content[data-empty=true]:before{content:attr(data-placeholder);position:absolute;color:#94a3b8;pointer-events:none;-webkit-user-select:none;user-select:none}.ngx-editor-content:has(ol,ul,li,table,img,hr,blockquote,pre):before{display:none!important;content:none!important}.ngx-editor-source{width:100%;flex:1;min-height:200px;padding:16px;border:none;outline:none;font-family:Consolas,Monaco,Courier New,monospace;font-size:13.5px;line-height:1.5;background-color:#0f172a;color:#38bdf8;resize:vertical;box-sizing:border-box}.ngx-editor-statusbar{display:flex;align-items:center;justify-content:space-between;padding:6px 12px;background-color:var(--ngx-editor-toolbar-bg);border-top:1px solid var(--ngx-editor-border);font-size:12px;color:var(--ngx-editor-muted)}.ngx-editor-statusbar .statusbar-left,.ngx-editor-statusbar .statusbar-right{display:flex;align-items:center;gap:12px}.ngx-editor-statusbar .status-tag{background-color:#e2e8f0;padding:2px 6px;border-radius:4px;font-weight:600;text-transform:uppercase;font-size:10px}.ngx-editor-statusbar .status-badge{background-color:#fee2e2;color:#ef4444;padding:2px 6px;border-radius:4px;font-weight:500;font-size:11px}.ngx-editor-modal-backdrop{position:absolute;inset:0;background:#0f172a66;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);display:flex;align-items:center;justify-content:center;z-index:100;animation:fadeIn .15s ease-out}.ngx-editor-modal{background:#fff;border-radius:8px;box-shadow:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a;width:90%;max-width:400px;padding:20px;box-sizing:border-box}.ngx-editor-modal .modal-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px}.ngx-editor-modal .modal-header h3{margin:0;font-size:16px;font-weight:600;color:var(--ngx-editor-text)}.ngx-editor-modal .modal-body{display:flex;flex-direction:column;gap:12px}.ngx-editor-modal .form-group{display:flex;flex-direction:column;gap:4px}.ngx-editor-modal .form-group label{font-size:13px;font-weight:500;color:var(--ngx-editor-text)}.ngx-editor-modal .form-group input[type=text],.ngx-editor-modal .form-group input[type=url]{padding:8px 12px;border:1px solid var(--ngx-editor-border);border-radius:6px;font-size:14px;outline:none;transition:border-color .15s ease}.ngx-editor-modal .form-group input[type=text]:focus,.ngx-editor-modal .form-group input[type=url]:focus{border-color:var(--ngx-editor-border-focus)}.ngx-editor-modal .form-group .checkbox-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-size:13px;color:var(--ngx-editor-text);-webkit-user-select:none;user-select:none}.ngx-editor-modal .modal-footer{display:flex;justify-content:flex-end;gap:8px;margin-top:20px}.ngx-editor-modal .modal-footer button{padding:7px 14px;border-radius:6px;font-size:13px;font-weight:500;cursor:pointer;transition:all .15s ease;outline:none}.ngx-editor-modal .modal-footer .btn-secondary{background:#f1f5f9;border:1px solid var(--ngx-editor-border);color:var(--ngx-editor-text)}.ngx-editor-modal .modal-footer .btn-secondary:hover{background:#e2e8f0}.ngx-editor-modal .modal-footer .btn-primary{background:#2563eb;border:1px solid #2563eb;color:#fff}.ngx-editor-modal .modal-footer .btn-primary:hover{background:#1d4ed8}.ngx-editor-modal .modal-footer .btn-primary:disabled{opacity:.5;cursor:not-allowed}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}\n"] }]
2716
3647
  }], ctorParameters: () => [{ type: EditorCommandService }, { type: i2.DomSanitizer }, { type: i0.ElementRef }, { type: i0.ChangeDetectorRef }], propDecorators: { editorContentRef: [{
2717
3648
  type: ViewChild,
2718
3649
  args: ['editorContent', { static: false }]