@flogeez/angular-tiptap-editor 2.2.1 → 2.2.3

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.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, output, ChangeDetectionStrategy, Component, signal, computed, Injectable, inject, effect, ViewChild, Directive, viewChild, DestroyRef, untracked } from '@angular/core';
2
+ import { input, output, ChangeDetectionStrategy, Component, signal, computed, Injectable, inject, viewChild, effect, Directive, DestroyRef, untracked } from '@angular/core';
3
3
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
4
4
  import { Node as Node$1, nodeInputRule, mergeAttributes, Extension, getAttributes, Editor } from '@tiptap/core';
5
5
  import StarterKit from '@tiptap/starter-kit';
@@ -134,10 +134,12 @@ const AteResizableImage = Node$1.create({
134
134
  img.alt = node.attrs["alt"] || "";
135
135
  img.title = node.attrs["title"] || "";
136
136
  img.className = "ate-image";
137
- if (node.attrs["width"])
137
+ if (node.attrs["width"]) {
138
138
  img.width = node.attrs["width"];
139
- if (node.attrs["height"])
139
+ }
140
+ if (node.attrs["height"]) {
140
141
  img.height = node.attrs["height"];
142
+ }
141
143
  img.parentNode?.insertBefore(container, img);
142
144
  container.appendChild(img);
143
145
  // Add modern resize controls
@@ -173,13 +175,16 @@ const AteResizableImage = Node$1.create({
173
175
  startX = e.clientX;
174
176
  startY = e.clientY;
175
177
  // Use current image dimensions instead of initial ones
176
- startWidth = parseInt(img.getAttribute("width") || "0") || node.attrs["width"] || img.naturalWidth;
177
- startHeight = parseInt(img.getAttribute("height") || "0") || node.attrs["height"] || img.naturalHeight;
178
+ startWidth =
179
+ parseInt(img.getAttribute("width") || "0") || node.attrs["width"] || img.naturalWidth;
180
+ startHeight =
181
+ parseInt(img.getAttribute("height") || "0") || node.attrs["height"] || img.naturalHeight;
178
182
  // Add resizing class to body
179
183
  document.body.classList.add("resizing");
180
184
  const handleMouseMove = (e) => {
181
- if (!isResizing)
185
+ if (!isResizing) {
182
186
  return;
187
+ }
183
188
  const deltaX = e.clientX - startX;
184
189
  const deltaY = e.clientY - startY;
185
190
  let newWidth = startWidth;
@@ -272,15 +277,18 @@ const AteResizableImage = Node$1.create({
272
277
  selectNode,
273
278
  deselectNode,
274
279
  update: updatedNode => {
275
- if (updatedNode.type.name !== "resizableImage")
280
+ if (updatedNode.type.name !== "resizableImage") {
276
281
  return false;
282
+ }
277
283
  img.src = updatedNode.attrs["src"];
278
284
  img.alt = updatedNode.attrs["alt"] || "";
279
285
  img.title = updatedNode.attrs["title"] || "";
280
- if (updatedNode.attrs["width"])
286
+ if (updatedNode.attrs["width"]) {
281
287
  img.width = updatedNode.attrs["width"];
282
- if (updatedNode.attrs["height"])
288
+ }
289
+ if (updatedNode.attrs["height"]) {
283
290
  img.height = updatedNode.attrs["height"];
291
+ }
284
292
  return true;
285
293
  },
286
294
  };
@@ -600,14 +608,16 @@ const ATE_INITIAL_EDITOR_STATE = {
600
608
  */
601
609
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
602
610
  function fastMerge(target, source) {
603
- if (!source)
611
+ if (!source) {
604
612
  return;
613
+ }
605
614
  for (const key in source) {
606
615
  const sourceVal = source[key];
607
616
  // If the value is an object (marks, can, nodes, selection)
608
617
  if (sourceVal !== null && typeof sourceVal === "object" && !Array.isArray(sourceVal)) {
609
- if (!target[key])
618
+ if (!target[key]) {
610
619
  target[key] = {};
620
+ }
611
621
  // Merge internal properties
612
622
  for (const subKey in sourceVal) {
613
623
  const subVal = sourceVal[subKey];
@@ -644,8 +654,9 @@ const AteTiptapStateExtension = Extension.create({
644
654
  view: () => ({
645
655
  update: _view => {
646
656
  const { editor } = this;
647
- if (!editor)
657
+ if (!editor) {
648
658
  return;
659
+ }
649
660
  const snapshot = createFreshSnapshot();
650
661
  const calcs = this.options.calculators;
651
662
  for (const calculator of calcs) {
@@ -1246,14 +1257,20 @@ class AteImageService {
1246
1257
  updateImageAttributes(editor, attributes) {
1247
1258
  if (editor.isActive("resizableImage")) {
1248
1259
  const pos = editor.state.selection.from;
1249
- editor.chain().focus().updateAttributes("resizableImage", attributes).setNodeSelection(pos).run();
1260
+ editor
1261
+ .chain()
1262
+ .focus()
1263
+ .updateAttributes("resizableImage", attributes)
1264
+ .setNodeSelection(pos)
1265
+ .run();
1250
1266
  this.updateSelectedImage(attributes);
1251
1267
  }
1252
1268
  }
1253
1269
  /** Resize image with optional aspect ratio maintenance */
1254
1270
  resizeImage(editor, options) {
1255
- if (!editor.isActive("resizableImage"))
1271
+ if (!editor.isActive("resizableImage")) {
1256
1272
  return;
1273
+ }
1257
1274
  const currentAttrs = editor.getAttributes("resizableImage");
1258
1275
  let newWidth = options.width;
1259
1276
  let newHeight = options.height;
@@ -1268,10 +1285,12 @@ class AteImageService {
1268
1285
  }
1269
1286
  }
1270
1287
  // Apply minimum limits
1271
- if (newWidth)
1288
+ if (newWidth) {
1272
1289
  newWidth = Math.max(50, newWidth);
1273
- if (newHeight)
1290
+ }
1291
+ if (newHeight) {
1274
1292
  newHeight = Math.max(50, newHeight);
1293
+ }
1275
1294
  this.updateImageAttributes(editor, {
1276
1295
  width: newWidth,
1277
1296
  height: newHeight,
@@ -1288,8 +1307,9 @@ class AteImageService {
1288
1307
  this.resizeImage(editor, { width: 800, height: 600, maintainAspectRatio: true });
1289
1308
  }
1290
1309
  resizeImageToOriginal(editor) {
1291
- if (!editor.isActive("resizableImage"))
1310
+ if (!editor.isActive("resizableImage")) {
1292
1311
  return;
1312
+ }
1293
1313
  const img = new Image();
1294
1314
  img.onload = () => {
1295
1315
  this.resizeImage(editor, { width: img.naturalWidth, height: img.naturalHeight });
@@ -1298,8 +1318,9 @@ class AteImageService {
1298
1318
  }
1299
1319
  /** Get current image dimensions */
1300
1320
  getImageDimensions(editor) {
1301
- if (!editor.isActive("resizableImage"))
1321
+ if (!editor.isActive("resizableImage")) {
1302
1322
  return null;
1323
+ }
1303
1324
  const attrs = editor.getAttributes("resizableImage");
1304
1325
  return {
1305
1326
  width: attrs["width"] || 0,
@@ -1396,8 +1417,9 @@ class AteImageService {
1396
1417
  maxSize: options?.maxSize,
1397
1418
  allowedTypes: options?.allowedTypes,
1398
1419
  });
1399
- if (!validation.valid)
1420
+ if (!validation.valid) {
1400
1421
  throw new Error(validation.error);
1422
+ }
1401
1423
  this.uploadProgress.set(30);
1402
1424
  this.uploadMessage.set(this.t().compressing);
1403
1425
  this.forceEditorUpdate();
@@ -1419,8 +1441,9 @@ class AteImageService {
1419
1441
  ? await firstValueFrom(handlerResponse)
1420
1442
  : await handlerResponse;
1421
1443
  result.src = handlerResult.src;
1422
- if (handlerResult.alt)
1444
+ if (handlerResult.alt) {
1423
1445
  result.name = handlerResult.alt;
1446
+ }
1424
1447
  }
1425
1448
  catch (handlerError) {
1426
1449
  console.error(this.t().uploadError, handlerError);
@@ -1528,7 +1551,11 @@ class AteImageService {
1528
1551
  };
1529
1552
  // If the image was active or is still active, update it atomically
1530
1553
  if (wasActive || ed.isActive("resizableImage")) {
1531
- ed.chain().focus().updateAttributes("resizableImage", imageData).setNodeSelection(pos).run();
1554
+ ed.chain()
1555
+ .focus()
1556
+ .updateAttributes("resizableImage", imageData)
1557
+ .setNodeSelection(pos)
1558
+ .run();
1532
1559
  this.updateSelectedImage(imageData);
1533
1560
  }
1534
1561
  else {
@@ -1550,20 +1577,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
1550
1577
  * Normalizes color values (rgb/rgba to hex).
1551
1578
  */
1552
1579
  function normalizeColor(color) {
1553
- if (!color || color === "transparent" || color === "rgba(0, 0, 0, 0)")
1580
+ if (!color || color === "transparent" || color === "rgba(0, 0, 0, 0)") {
1554
1581
  return null;
1555
- if (color.startsWith("#"))
1582
+ }
1583
+ if (color.startsWith("#")) {
1556
1584
  return color;
1585
+ }
1557
1586
  // Support both comma-separated (legacy) and space-separated (modern) rgb/rgba
1558
- const rgbMatch = color.trim().match(/^rgba?\(\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)(?:[,\s/]+([\d.]+))?\s*\)$/i);
1587
+ const rgbMatch = color
1588
+ .trim()
1589
+ .match(/^rgba?\(\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)(?:[,\s/]+([\d.]+))?\s*\)$/i);
1559
1590
  // If it's a named color or something else we don't recognize,
1560
1591
  // we can't easily normalize it to hex without a heavy dictionary.
1561
1592
  // However, getComputedStyle usually returns rgb() which we handle above.
1562
1593
  if (!rgbMatch) {
1563
1594
  // Simple fallback for common named colors if getComputedStyle fails to return rgb
1564
1595
  const named = { black: "#000000", white: "#ffffff", red: "#ff0000" };
1565
- if (named[color.toLowerCase()])
1596
+ if (named[color.toLowerCase()]) {
1566
1597
  return named[color.toLowerCase()];
1598
+ }
1567
1599
  return null;
1568
1600
  }
1569
1601
  const r = Math.max(0, Math.min(255, Math.round(parseFloat(rgbMatch[1]))));
@@ -1571,8 +1603,9 @@ function normalizeColor(color) {
1571
1603
  const b = Math.max(0, Math.min(255, Math.round(parseFloat(rgbMatch[3]))));
1572
1604
  const a = rgbMatch[4] ? parseFloat(rgbMatch[4]) : 1;
1573
1605
  // If completely transparent, return null
1574
- if (a === 0)
1606
+ if (a === 0) {
1575
1607
  return null;
1608
+ }
1576
1609
  return ("#" +
1577
1610
  [r, g, b]
1578
1611
  .map(n => n.toString(16).padStart(2, "0"))
@@ -1584,8 +1617,9 @@ function normalizeColor(color) {
1584
1617
  */
1585
1618
  function getLuminance(color) {
1586
1619
  const normalized = normalizeColor(color);
1587
- if (!normalized)
1588
- return 0; // Default to dark (luminance 0) if no color
1620
+ if (!normalized) {
1621
+ return 0;
1622
+ } // Default to dark (luminance 0) if no color
1589
1623
  const hex = normalized.replace("#", "");
1590
1624
  const r = parseInt(hex.substring(0, 2), 16);
1591
1625
  const g = parseInt(hex.substring(2, 4), 16);
@@ -1639,8 +1673,9 @@ class AteColorPickerService {
1639
1673
  * Toggle color picker from UI (extracts trigger from event).
1640
1674
  */
1641
1675
  toggle(editor, mode, event) {
1642
- if (!editor)
1676
+ if (!editor) {
1643
1677
  return;
1678
+ }
1644
1679
  let trigger;
1645
1680
  if (event && typeof event !== "string") {
1646
1681
  const target = event.target;
@@ -1655,8 +1690,9 @@ class AteColorPickerService {
1655
1690
  * Capture current editor selection.
1656
1691
  */
1657
1692
  captureSelection(editor) {
1658
- if (!editor)
1693
+ if (!editor) {
1659
1694
  return;
1695
+ }
1660
1696
  this.storedSelection = {
1661
1697
  from: editor.state.selection.from,
1662
1698
  to: editor.state.selection.to,
@@ -1682,42 +1718,44 @@ class AteColorPickerService {
1682
1718
  * Apply text color to selection.
1683
1719
  */
1684
1720
  applyColor(editor, color, addToHistory = true, focus = true) {
1685
- if (!editor)
1721
+ if (!editor) {
1686
1722
  return;
1723
+ }
1687
1724
  const stored = this.storedSelection;
1688
1725
  let chain = editor.chain();
1689
- if (focus)
1726
+ if (focus) {
1690
1727
  chain = chain.focus();
1728
+ }
1691
1729
  if (stored && (editor.state.selection.empty || !editor.isFocused)) {
1692
1730
  chain = chain.setTextSelection(stored);
1693
1731
  }
1694
1732
  else if (editor.state.selection.empty && !stored) {
1695
1733
  chain = chain.extendMarkRange("textStyle");
1696
1734
  }
1697
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1698
1735
  chain.setColor(color);
1699
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1700
- if (addToHistory === false)
1736
+ if (addToHistory === false) {
1701
1737
  chain = chain.setMeta("addToHistory", false);
1738
+ }
1702
1739
  chain.run();
1703
1740
  }
1704
1741
  /**
1705
1742
  * Remove text color from selection.
1706
1743
  */
1707
1744
  unsetColor(editor, focus = true) {
1708
- if (!editor)
1745
+ if (!editor) {
1709
1746
  return;
1747
+ }
1710
1748
  const stored = this.storedSelection;
1711
1749
  let chain = editor.chain();
1712
- if (focus)
1750
+ if (focus) {
1713
1751
  chain = chain.focus();
1752
+ }
1714
1753
  if (stored) {
1715
1754
  chain = chain.setTextSelection(stored);
1716
1755
  }
1717
1756
  else if (editor.state.selection.empty) {
1718
1757
  chain = chain.extendMarkRange("textStyle");
1719
1758
  }
1720
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1721
1759
  chain.unsetColor();
1722
1760
  chain.run();
1723
1761
  }
@@ -1725,42 +1763,44 @@ class AteColorPickerService {
1725
1763
  * Apply highlight color to selection.
1726
1764
  */
1727
1765
  applyHighlight(editor, color, addToHistory = true, focus = true) {
1728
- if (!editor)
1766
+ if (!editor) {
1729
1767
  return;
1768
+ }
1730
1769
  const stored = this.storedSelection;
1731
1770
  let chain = editor.chain();
1732
- if (focus)
1771
+ if (focus) {
1733
1772
  chain = chain.focus();
1773
+ }
1734
1774
  if (stored && (editor.state.selection.empty || !editor.isFocused)) {
1735
1775
  chain = chain.setTextSelection(stored);
1736
1776
  }
1737
1777
  else if (editor.state.selection.empty && !stored) {
1738
1778
  chain = chain.extendMarkRange("highlight");
1739
1779
  }
1740
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1741
1780
  chain.setHighlight({ color });
1742
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1743
- if (addToHistory === false)
1781
+ if (addToHistory === false) {
1744
1782
  chain = chain.setMeta("addToHistory", false);
1783
+ }
1745
1784
  chain.run();
1746
1785
  }
1747
1786
  /**
1748
1787
  * Remove highlight from selection.
1749
1788
  */
1750
1789
  unsetHighlight(editor, focus = true) {
1751
- if (!editor)
1790
+ if (!editor) {
1752
1791
  return;
1792
+ }
1753
1793
  const stored = this.storedSelection;
1754
1794
  let chain = editor.chain();
1755
- if (focus)
1795
+ if (focus) {
1756
1796
  chain = chain.focus();
1797
+ }
1757
1798
  if (stored) {
1758
1799
  chain = chain.setTextSelection(stored);
1759
1800
  }
1760
1801
  else if (editor.state.selection.empty) {
1761
1802
  chain = chain.extendMarkRange("highlight");
1762
1803
  }
1763
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1764
1804
  chain.unsetHighlight();
1765
1805
  chain.run();
1766
1806
  }
@@ -1830,8 +1870,9 @@ class AteLinkService {
1830
1870
  * If an Event is provided, extracts the trigger for anchoring.
1831
1871
  */
1832
1872
  toggle(editor, urlOrEvent) {
1833
- if (!editor)
1873
+ if (!editor) {
1834
1874
  return;
1875
+ }
1835
1876
  // If a string URL is provided, set the link and close
1836
1877
  if (urlOrEvent && typeof urlOrEvent === "string") {
1837
1878
  this.setLink(editor, urlOrEvent);
@@ -1841,7 +1882,8 @@ class AteLinkService {
1841
1882
  let trigger;
1842
1883
  if (urlOrEvent && typeof urlOrEvent !== "string") {
1843
1884
  const target = urlOrEvent.target;
1844
- trigger = urlOrEvent.currentTarget || target?.closest("button") || target;
1885
+ trigger =
1886
+ urlOrEvent.currentTarget || target?.closest("button") || target;
1845
1887
  }
1846
1888
  // Open the edit menu
1847
1889
  this.open(trigger);
@@ -1853,8 +1895,9 @@ class AteLinkService {
1853
1895
  * Apply a link to the current selection.
1854
1896
  */
1855
1897
  setLink(editor, url) {
1856
- if (!editor)
1898
+ if (!editor) {
1857
1899
  return;
1900
+ }
1858
1901
  editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run();
1859
1902
  this.close();
1860
1903
  }
@@ -1862,8 +1905,9 @@ class AteLinkService {
1862
1905
  * Remove link from the current selection.
1863
1906
  */
1864
1907
  unsetLink(editor) {
1865
- if (!editor)
1908
+ if (!editor) {
1866
1909
  return;
1910
+ }
1867
1911
  editor.chain().focus().extendMarkRange("link").unsetLink().run();
1868
1912
  this.close();
1869
1913
  }
@@ -1881,64 +1925,78 @@ class AteEditorCommandsService {
1881
1925
  this.linkSvc = inject(AteLinkService);
1882
1926
  this._editorState = signal(ATE_INITIAL_EDITOR_STATE, ...(ngDevMode ? [{ debugName: "_editorState", equal: (a, b) => {
1883
1927
  // 1. Primitive global states
1884
- if (a.isFocused !== b.isFocused || a.isEditable !== b.isEditable)
1928
+ if (a.isFocused !== b.isFocused || a.isEditable !== b.isEditable) {
1885
1929
  return false;
1930
+ }
1886
1931
  // 2. Detailed selection comparison
1887
1932
  if (a.selection.from !== b.selection.from ||
1888
1933
  a.selection.to !== b.selection.to ||
1889
1934
  a.selection.type !== b.selection.type ||
1890
1935
  a.selection.empty !== b.selection.empty ||
1891
- a.selection.isSingleCell !== b.selection.isSingleCell)
1936
+ a.selection.isSingleCell !== b.selection.isSingleCell) {
1892
1937
  return false;
1938
+ }
1893
1939
  // Helper for object comparison
1894
1940
  const isRecordEqual = (objA, objB) => {
1895
1941
  const keysA = Object.keys(objA);
1896
1942
  const keysB = Object.keys(objB);
1897
- if (keysA.length !== keysB.length)
1943
+ if (keysA.length !== keysB.length) {
1898
1944
  return false;
1945
+ }
1899
1946
  return keysA.every(key => objA[key] === objB[key]);
1900
1947
  };
1901
1948
  // 3. Compare sub-states (marks, can, nodes)
1902
- if (!isRecordEqual(a.marks, b.marks))
1949
+ if (!isRecordEqual(a.marks, b.marks)) {
1903
1950
  return false;
1904
- if (!isRecordEqual(a.can, b.can))
1951
+ }
1952
+ if (!isRecordEqual(a.can, b.can)) {
1905
1953
  return false;
1906
- if (!isRecordEqual(a.nodes, b.nodes))
1954
+ }
1955
+ if (!isRecordEqual(a.nodes, b.nodes)) {
1907
1956
  return false;
1957
+ }
1908
1958
  // 4. Compare custom extension states
1909
- if (!isRecordEqual(a.custom, b.custom))
1959
+ if (!isRecordEqual(a.custom, b.custom)) {
1910
1960
  return false;
1961
+ }
1911
1962
  return true;
1912
1963
  } }] : [{
1913
1964
  equal: (a, b) => {
1914
1965
  // 1. Primitive global states
1915
- if (a.isFocused !== b.isFocused || a.isEditable !== b.isEditable)
1966
+ if (a.isFocused !== b.isFocused || a.isEditable !== b.isEditable) {
1916
1967
  return false;
1968
+ }
1917
1969
  // 2. Detailed selection comparison
1918
1970
  if (a.selection.from !== b.selection.from ||
1919
1971
  a.selection.to !== b.selection.to ||
1920
1972
  a.selection.type !== b.selection.type ||
1921
1973
  a.selection.empty !== b.selection.empty ||
1922
- a.selection.isSingleCell !== b.selection.isSingleCell)
1974
+ a.selection.isSingleCell !== b.selection.isSingleCell) {
1923
1975
  return false;
1976
+ }
1924
1977
  // Helper for object comparison
1925
1978
  const isRecordEqual = (objA, objB) => {
1926
1979
  const keysA = Object.keys(objA);
1927
1980
  const keysB = Object.keys(objB);
1928
- if (keysA.length !== keysB.length)
1981
+ if (keysA.length !== keysB.length) {
1929
1982
  return false;
1983
+ }
1930
1984
  return keysA.every(key => objA[key] === objB[key]);
1931
1985
  };
1932
1986
  // 3. Compare sub-states (marks, can, nodes)
1933
- if (!isRecordEqual(a.marks, b.marks))
1987
+ if (!isRecordEqual(a.marks, b.marks)) {
1934
1988
  return false;
1935
- if (!isRecordEqual(a.can, b.can))
1989
+ }
1990
+ if (!isRecordEqual(a.can, b.can)) {
1936
1991
  return false;
1937
- if (!isRecordEqual(a.nodes, b.nodes))
1992
+ }
1993
+ if (!isRecordEqual(a.nodes, b.nodes)) {
1938
1994
  return false;
1995
+ }
1939
1996
  // 4. Compare custom extension states
1940
- if (!isRecordEqual(a.custom, b.custom))
1997
+ if (!isRecordEqual(a.custom, b.custom)) {
1941
1998
  return false;
1999
+ }
1942
2000
  return true;
1943
2001
  },
1944
2002
  }]));
@@ -1988,8 +2046,9 @@ class AteEditorCommandsService {
1988
2046
  // ============================================
1989
2047
  /** Generic method to execute any command by name */
1990
2048
  execute(editor, command, ...args) {
1991
- if (!editor)
2049
+ if (!editor) {
1992
2050
  return;
2051
+ }
1993
2052
  switch (command) {
1994
2053
  case "toggleBold":
1995
2054
  this.toggleBold(editor);
@@ -2112,53 +2171,63 @@ class AteEditorCommandsService {
2112
2171
  }
2113
2172
  // --- Formatting Commands ---
2114
2173
  toggleBold(editor) {
2115
- if (!editor)
2174
+ if (!editor) {
2116
2175
  return;
2176
+ }
2117
2177
  editor.chain().focus().toggleBold().run();
2118
2178
  }
2119
2179
  toggleItalic(editor) {
2120
- if (!editor)
2180
+ if (!editor) {
2121
2181
  return;
2182
+ }
2122
2183
  editor.chain().focus().toggleItalic().run();
2123
2184
  }
2124
2185
  toggleStrike(editor) {
2125
- if (!editor)
2186
+ if (!editor) {
2126
2187
  return;
2188
+ }
2127
2189
  editor.chain().focus().toggleStrike().run();
2128
2190
  }
2129
2191
  toggleCode(editor) {
2130
- if (!editor)
2192
+ if (!editor) {
2131
2193
  return;
2194
+ }
2132
2195
  editor.chain().focus().toggleCode().run();
2133
2196
  }
2134
2197
  toggleCodeBlock(editor) {
2135
- if (!editor)
2198
+ if (!editor) {
2136
2199
  return;
2200
+ }
2137
2201
  editor.chain().focus().toggleCodeBlock().run();
2138
2202
  }
2139
2203
  toggleUnderline(editor) {
2140
- if (!editor)
2204
+ if (!editor) {
2141
2205
  return;
2206
+ }
2142
2207
  editor.chain().focus().toggleUnderline().run();
2143
2208
  }
2144
2209
  toggleSuperscript(editor) {
2145
- if (!editor)
2210
+ if (!editor) {
2146
2211
  return;
2212
+ }
2147
2213
  editor.chain().focus().toggleSuperscript().run();
2148
2214
  }
2149
2215
  toggleSubscript(editor) {
2150
- if (!editor)
2216
+ if (!editor) {
2151
2217
  return;
2218
+ }
2152
2219
  editor.chain().focus().toggleSubscript().run();
2153
2220
  }
2154
2221
  toggleHeading(editor, level) {
2155
- if (!editor)
2222
+ if (!editor) {
2156
2223
  return;
2224
+ }
2157
2225
  editor.chain().focus().toggleHeading({ level }).run();
2158
2226
  }
2159
2227
  toggleHighlight(editor, color) {
2160
- if (!editor)
2228
+ if (!editor) {
2161
2229
  return;
2230
+ }
2162
2231
  if (color) {
2163
2232
  editor.chain().focus().setHighlight({ color }).run();
2164
2233
  }
@@ -2168,131 +2237,156 @@ class AteEditorCommandsService {
2168
2237
  }
2169
2238
  // --- Structure Commands ---
2170
2239
  toggleBulletList(editor) {
2171
- if (!editor)
2240
+ if (!editor) {
2172
2241
  return;
2242
+ }
2173
2243
  editor.chain().focus().toggleBulletList().run();
2174
2244
  }
2175
2245
  toggleOrderedList(editor) {
2176
- if (!editor)
2246
+ if (!editor) {
2177
2247
  return;
2248
+ }
2178
2249
  editor.chain().focus().toggleOrderedList().run();
2179
2250
  }
2180
2251
  toggleBlockquote(editor) {
2181
- if (!editor)
2252
+ if (!editor) {
2182
2253
  return;
2254
+ }
2183
2255
  editor.chain().focus().toggleBlockquote().run();
2184
2256
  }
2185
2257
  setTextAlign(editor, alignment) {
2186
- if (!editor)
2258
+ if (!editor) {
2187
2259
  return;
2260
+ }
2188
2261
  editor.chain().focus().setTextAlign(alignment).run();
2189
2262
  }
2190
2263
  insertHorizontalRule(editor) {
2191
- if (!editor)
2264
+ if (!editor) {
2192
2265
  return;
2266
+ }
2193
2267
  editor.chain().focus().setHorizontalRule().run();
2194
2268
  }
2195
2269
  // --- History Commands ---
2196
2270
  undo(editor) {
2197
- if (!editor)
2271
+ if (!editor) {
2198
2272
  return;
2273
+ }
2199
2274
  editor.chain().focus().undo().run();
2200
2275
  }
2201
2276
  redo(editor) {
2202
- if (!editor)
2277
+ if (!editor) {
2203
2278
  return;
2279
+ }
2204
2280
  editor.chain().focus().redo().run();
2205
2281
  }
2206
2282
  // --- Table Commands ---
2207
2283
  insertTable(editor, rows = 3, cols = 3) {
2208
- if (!editor)
2284
+ if (!editor) {
2209
2285
  return;
2286
+ }
2210
2287
  editor.chain().focus().insertTable({ rows, cols }).run();
2211
2288
  }
2212
2289
  addColumnBefore(editor) {
2213
- if (!editor)
2290
+ if (!editor) {
2214
2291
  return;
2292
+ }
2215
2293
  editor.chain().focus().addColumnBefore().run();
2216
2294
  }
2217
2295
  addColumnAfter(editor) {
2218
- if (!editor)
2296
+ if (!editor) {
2219
2297
  return;
2298
+ }
2220
2299
  editor.chain().focus().addColumnAfter().run();
2221
2300
  }
2222
2301
  deleteColumn(editor) {
2223
- if (!editor)
2302
+ if (!editor) {
2224
2303
  return;
2304
+ }
2225
2305
  editor.chain().focus().deleteColumn().run();
2226
2306
  }
2227
2307
  addRowBefore(editor) {
2228
- if (!editor)
2308
+ if (!editor) {
2229
2309
  return;
2310
+ }
2230
2311
  editor.chain().focus().addRowBefore().run();
2231
2312
  }
2232
2313
  addRowAfter(editor) {
2233
- if (!editor)
2314
+ if (!editor) {
2234
2315
  return;
2316
+ }
2235
2317
  editor.chain().focus().addRowAfter().run();
2236
2318
  }
2237
2319
  deleteRow(editor) {
2238
- if (!editor)
2320
+ if (!editor) {
2239
2321
  return;
2322
+ }
2240
2323
  editor.chain().focus().deleteRow().run();
2241
2324
  }
2242
2325
  deleteTable(editor) {
2243
- if (!editor)
2326
+ if (!editor) {
2244
2327
  return;
2328
+ }
2245
2329
  editor.chain().focus().deleteTable().run();
2246
2330
  }
2247
2331
  mergeCells(editor) {
2248
- if (!editor)
2332
+ if (!editor) {
2249
2333
  return;
2334
+ }
2250
2335
  editor.chain().focus().mergeCells().run();
2251
2336
  }
2252
2337
  splitCell(editor) {
2253
- if (!editor)
2338
+ if (!editor) {
2254
2339
  return;
2340
+ }
2255
2341
  editor.chain().focus().splitCell().run();
2256
2342
  }
2257
2343
  toggleHeaderColumn(editor) {
2258
- if (!editor)
2344
+ if (!editor) {
2259
2345
  return;
2346
+ }
2260
2347
  editor.chain().focus().toggleHeaderColumn().run();
2261
2348
  }
2262
2349
  toggleHeaderRow(editor) {
2263
- if (!editor)
2350
+ if (!editor) {
2264
2351
  return;
2352
+ }
2265
2353
  editor.chain().focus().toggleHeaderRow().run();
2266
2354
  }
2267
2355
  // --- Utility Commands ---
2268
2356
  clearContent(editor) {
2269
- if (!editor)
2357
+ if (!editor) {
2270
2358
  return;
2359
+ }
2271
2360
  editor.commands.clearContent(true);
2272
2361
  }
2273
2362
  focus(editor) {
2274
- if (!editor)
2363
+ if (!editor) {
2275
2364
  return;
2365
+ }
2276
2366
  editor.chain().focus().run();
2277
2367
  }
2278
2368
  blur(editor) {
2279
- if (!editor)
2369
+ if (!editor) {
2280
2370
  return;
2371
+ }
2281
2372
  editor.chain().blur().run();
2282
2373
  }
2283
2374
  setContent(editor, content, emitUpdate = true) {
2284
- if (!editor)
2375
+ if (!editor) {
2285
2376
  return;
2377
+ }
2286
2378
  editor.commands.setContent(content, emitUpdate);
2287
2379
  }
2288
2380
  setEditable(editor, editable) {
2289
- if (!editor)
2381
+ if (!editor) {
2290
2382
  return;
2383
+ }
2291
2384
  editor.setEditable(editable);
2292
2385
  }
2293
2386
  insertContent(editor, content) {
2294
- if (!editor)
2387
+ if (!editor) {
2295
2388
  return;
2389
+ }
2296
2390
  editor.chain().focus().insertContent(content).run();
2297
2391
  }
2298
2392
  async insertImage(editor, options) {
@@ -2448,8 +2542,9 @@ class AteToolbarComponent {
2448
2542
  }
2449
2543
  onCommand(command, ...args) {
2450
2544
  const editor = this.editor();
2451
- if (!editor)
2545
+ if (!editor) {
2452
2546
  return;
2547
+ }
2453
2548
  this.editorCommands.execute(editor, command, ...args);
2454
2549
  }
2455
2550
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AteToolbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
@@ -2528,7 +2623,10 @@ class AteToolbarComponent {
2528
2623
  (buttonClick)="onCommand('toggleHighlight')" />
2529
2624
  }
2530
2625
  @if (config().highlightPicker) {
2531
- <ate-color-picker mode="highlight" [editor]="editor()" [disabled]="!state().can.setHighlight" />
2626
+ <ate-color-picker
2627
+ mode="highlight"
2628
+ [editor]="editor()"
2629
+ [disabled]="!state().can.setHighlight" />
2532
2630
  }
2533
2631
  @if (config().textColor) {
2534
2632
  <ate-color-picker mode="text" [editor]="editor()" [disabled]="!state().can.setColor" />
@@ -2561,7 +2659,9 @@ class AteToolbarComponent {
2561
2659
  [disabled]="!state().can.toggleHeading3"
2562
2660
  (buttonClick)="onCommand('toggleHeading', 3)" />
2563
2661
  }
2564
- @if (config().separator && (config().bulletList || config().orderedList || config().blockquote)) {
2662
+ @if (
2663
+ config().separator && (config().bulletList || config().orderedList || config().blockquote)
2664
+ ) {
2565
2665
  <ate-separator />
2566
2666
  }
2567
2667
  @if (config().bulletList) {
@@ -2665,10 +2765,18 @@ class AteToolbarComponent {
2665
2765
  <ate-separator />
2666
2766
  }
2667
2767
  @if (config().undo) {
2668
- <ate-button icon="undo" [title]="t().undo" [disabled]="!state().can.undo" (buttonClick)="onCommand('undo')" />
2768
+ <ate-button
2769
+ icon="undo"
2770
+ [title]="t().undo"
2771
+ [disabled]="!state().can.undo"
2772
+ (buttonClick)="onCommand('undo')" />
2669
2773
  }
2670
2774
  @if (config().redo) {
2671
- <ate-button icon="redo" [title]="t().redo" [disabled]="!state().can.redo" (buttonClick)="onCommand('redo')" />
2775
+ <ate-button
2776
+ icon="redo"
2777
+ [title]="t().redo"
2778
+ [disabled]="!state().can.redo"
2779
+ (buttonClick)="onCommand('redo')" />
2672
2780
  }
2673
2781
  @if (config().separator && config().clear) {
2674
2782
  <ate-separator />
@@ -2682,7 +2790,10 @@ class AteToolbarComponent {
2682
2790
  }
2683
2791
  @if (config().custom?.length) {
2684
2792
  @for (item of config().custom; track item.key) {
2685
- <ate-button [icon]="item.icon" [title]="item.label" (buttonClick)="item.command(editor())"></ate-button>
2793
+ <ate-button
2794
+ [icon]="item.icon"
2795
+ [title]="item.label"
2796
+ (buttonClick)="item.command(editor())"></ate-button>
2686
2797
  }
2687
2798
  }
2688
2799
  </div>
@@ -2765,7 +2876,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
2765
2876
  (buttonClick)="onCommand('toggleHighlight')" />
2766
2877
  }
2767
2878
  @if (config().highlightPicker) {
2768
- <ate-color-picker mode="highlight" [editor]="editor()" [disabled]="!state().can.setHighlight" />
2879
+ <ate-color-picker
2880
+ mode="highlight"
2881
+ [editor]="editor()"
2882
+ [disabled]="!state().can.setHighlight" />
2769
2883
  }
2770
2884
  @if (config().textColor) {
2771
2885
  <ate-color-picker mode="text" [editor]="editor()" [disabled]="!state().can.setColor" />
@@ -2798,7 +2912,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
2798
2912
  [disabled]="!state().can.toggleHeading3"
2799
2913
  (buttonClick)="onCommand('toggleHeading', 3)" />
2800
2914
  }
2801
- @if (config().separator && (config().bulletList || config().orderedList || config().blockquote)) {
2915
+ @if (
2916
+ config().separator && (config().bulletList || config().orderedList || config().blockquote)
2917
+ ) {
2802
2918
  <ate-separator />
2803
2919
  }
2804
2920
  @if (config().bulletList) {
@@ -2902,10 +3018,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
2902
3018
  <ate-separator />
2903
3019
  }
2904
3020
  @if (config().undo) {
2905
- <ate-button icon="undo" [title]="t().undo" [disabled]="!state().can.undo" (buttonClick)="onCommand('undo')" />
3021
+ <ate-button
3022
+ icon="undo"
3023
+ [title]="t().undo"
3024
+ [disabled]="!state().can.undo"
3025
+ (buttonClick)="onCommand('undo')" />
2906
3026
  }
2907
3027
  @if (config().redo) {
2908
- <ate-button icon="redo" [title]="t().redo" [disabled]="!state().can.redo" (buttonClick)="onCommand('redo')" />
3028
+ <ate-button
3029
+ icon="redo"
3030
+ [title]="t().redo"
3031
+ [disabled]="!state().can.redo"
3032
+ (buttonClick)="onCommand('redo')" />
2909
3033
  }
2910
3034
  @if (config().separator && config().clear) {
2911
3035
  <ate-separator />
@@ -2919,7 +3043,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
2919
3043
  }
2920
3044
  @if (config().custom?.length) {
2921
3045
  @for (item of config().custom; track item.key) {
2922
- <ate-button [icon]="item.icon" [title]="item.label" (buttonClick)="item.command(editor())"></ate-button>
3046
+ <ate-button
3047
+ [icon]="item.icon"
3048
+ [title]="item.label"
3049
+ (buttonClick)="item.command(editor())"></ate-button>
2923
3050
  }
2924
3051
  }
2925
3052
  </div>
@@ -2936,6 +3063,8 @@ class AteBaseBubbleMenu {
2936
3063
  this.editorCommands = inject(AteEditorCommandsService);
2937
3064
  // Core Inputs
2938
3065
  this.editor = input.required(...(ngDevMode ? [{ debugName: "editor" }] : []));
3066
+ // Required viewChild for the menu container
3067
+ this.menuRef = viewChild.required("menuRef");
2939
3068
  // Internal State
2940
3069
  this.tippyInstance = null;
2941
3070
  this.updateTimeout = null;
@@ -2952,8 +3081,9 @@ class AteBaseBubbleMenu {
2952
3081
  }
2953
3082
  this.updateTimeout = window.setTimeout(() => {
2954
3083
  const ed = this.editor();
2955
- if (!ed)
3084
+ if (!ed) {
2956
3085
  return;
3086
+ }
2957
3087
  // Hide when interacting with the main toolbar
2958
3088
  if (this.isToolbarInteracting()) {
2959
3089
  this.hideTippy();
@@ -2995,7 +3125,7 @@ class AteBaseBubbleMenu {
2995
3125
  * Can be overridden for specific Tippy configurations.
2996
3126
  */
2997
3127
  initTippy() {
2998
- if (!this.menuRef?.nativeElement) {
3128
+ if (!this.menuRef()?.nativeElement) {
2999
3129
  // Re-try if the view child is not yet available
3000
3130
  setTimeout(() => this.initTippy(), 50);
3001
3131
  return;
@@ -3005,7 +3135,7 @@ class AteBaseBubbleMenu {
3005
3135
  this.tippyInstance.destroy();
3006
3136
  }
3007
3137
  this.tippyInstance = tippy(document.body, {
3008
- content: this.menuRef.nativeElement,
3138
+ content: this.menuRef().nativeElement,
3009
3139
  trigger: "manual",
3010
3140
  placement: "top-start",
3011
3141
  appendTo: () => (ed ? ed.options.element : document.body),
@@ -3042,8 +3172,9 @@ class AteBaseBubbleMenu {
3042
3172
  * Helper to show the Tippy instance with updated positioning.
3043
3173
  */
3044
3174
  showTippy() {
3045
- if (!this.tippyInstance)
3175
+ if (!this.tippyInstance) {
3046
3176
  return;
3177
+ }
3047
3178
  // Update position before showing
3048
3179
  this.tippyInstance.setProps({
3049
3180
  getReferenceClientRect: () => this.getSelectionRect(),
@@ -3079,11 +3210,13 @@ class AteBaseBubbleMenu {
3079
3210
  * Uses a combination of native selection and ProseMirror coordinates.
3080
3211
  */
3081
3212
  getRectForSelection(ed) {
3082
- if (!ed)
3213
+ if (!ed) {
3083
3214
  return new DOMRect(0, 0, 0, 0);
3215
+ }
3084
3216
  const { from, to, empty } = ed.state.selection;
3085
- if (empty)
3217
+ if (empty) {
3086
3218
  return new DOMRect(-9999, -9999, 0, 0);
3219
+ }
3087
3220
  // 1. Try native selection for multi-line accuracy
3088
3221
  const selection = window.getSelection();
3089
3222
  if (selection && selection.rangeCount > 0) {
@@ -3116,14 +3249,11 @@ class AteBaseBubbleMenu {
3116
3249
  /* empty */
3117
3250
  }
3118
3251
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AteBaseBubbleMenu, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
3119
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.16", type: AteBaseBubbleMenu, isStandalone: true, inputs: { editor: { classPropertyName: "editor", publicName: "editor", isSignal: true, isRequired: true, transformFunction: null } }, viewQueries: [{ propertyName: "menuRef", first: true, predicate: ["menuRef"], descendants: true }], ngImport: i0 }); }
3252
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "20.3.16", type: AteBaseBubbleMenu, isStandalone: true, inputs: { editor: { classPropertyName: "editor", publicName: "editor", isSignal: true, isRequired: true, transformFunction: null } }, viewQueries: [{ propertyName: "menuRef", first: true, predicate: ["menuRef"], descendants: true, isSignal: true }], ngImport: i0 }); }
3120
3253
  }
3121
3254
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AteBaseBubbleMenu, decorators: [{
3122
3255
  type: Directive
3123
- }], ctorParameters: () => [], propDecorators: { editor: [{ type: i0.Input, args: [{ isSignal: true, alias: "editor", required: true }] }], menuRef: [{
3124
- type: ViewChild,
3125
- args: ["menuRef", { static: false }]
3126
- }] } });
3256
+ }], ctorParameters: () => [], propDecorators: { editor: [{ type: i0.Input, args: [{ isSignal: true, alias: "editor", required: true }] }], menuRef: [{ type: i0.ViewChild, args: ["menuRef", { isSignal: true }] }] } });
3127
3257
 
3128
3258
  class AteBubbleMenuComponent extends AteBaseBubbleMenu {
3129
3259
  constructor() {
@@ -3194,7 +3324,7 @@ class AteBubbleMenuComponent extends AteBaseBubbleMenu {
3194
3324
  }
3195
3325
  // Only show text bubble menu if there is a non-empty text selection
3196
3326
  // and no higher-priority node (image, table) is selected.
3197
- return selection.type === "text" && !selection.empty && !nodes.isImage && !nodes.isTableNodeSelected;
3327
+ return (selection.type === "text" && !selection.empty && !nodes.isImage && !nodes.isTableNodeSelected);
3198
3328
  }
3199
3329
  getSelectionRect() {
3200
3330
  return this.getRectForSelection(this.editor());
@@ -3281,7 +3411,11 @@ class AteBubbleMenuComponent extends AteBaseBubbleMenu {
3281
3411
  [anchorToText]="true" />
3282
3412
  }
3283
3413
  @if (bubbleMenuConfig().textColor) {
3284
- <ate-color-picker mode="text" [editor]="editor()" [disabled]="!state().can.setColor" [anchorToText]="true" />
3414
+ <ate-color-picker
3415
+ mode="text"
3416
+ [editor]="editor()"
3417
+ [disabled]="!state().can.setColor"
3418
+ [anchorToText]="true" />
3285
3419
  }
3286
3420
  @if (bubbleMenuConfig().link) {
3287
3421
  <ate-button
@@ -3293,7 +3427,10 @@ class AteBubbleMenuComponent extends AteBaseBubbleMenu {
3293
3427
  }
3294
3428
  @if (bubbleMenuConfig().custom?.length) {
3295
3429
  @for (item of bubbleMenuConfig().custom; track item.key) {
3296
- <ate-button [icon]="item.icon" [title]="item.label" (buttonClick)="item.command(editor())"></ate-button>
3430
+ <ate-button
3431
+ [icon]="item.icon"
3432
+ [title]="item.label"
3433
+ (buttonClick)="item.command(editor())"></ate-button>
3297
3434
  }
3298
3435
  }
3299
3436
  </div>
@@ -3380,7 +3517,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
3380
3517
  [anchorToText]="true" />
3381
3518
  }
3382
3519
  @if (bubbleMenuConfig().textColor) {
3383
- <ate-color-picker mode="text" [editor]="editor()" [disabled]="!state().can.setColor" [anchorToText]="true" />
3520
+ <ate-color-picker
3521
+ mode="text"
3522
+ [editor]="editor()"
3523
+ [disabled]="!state().can.setColor"
3524
+ [anchorToText]="true" />
3384
3525
  }
3385
3526
  @if (bubbleMenuConfig().link) {
3386
3527
  <ate-button
@@ -3392,7 +3533,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
3392
3533
  }
3393
3534
  @if (bubbleMenuConfig().custom?.length) {
3394
3535
  @for (item of bubbleMenuConfig().custom; track item.key) {
3395
- <ate-button [icon]="item.icon" [title]="item.label" (buttonClick)="item.command(editor())"></ate-button>
3536
+ <ate-button
3537
+ [icon]="item.icon"
3538
+ [title]="item.label"
3539
+ (buttonClick)="item.command(editor())"></ate-button>
3396
3540
  }
3397
3541
  }
3398
3542
  </div>
@@ -3438,8 +3582,9 @@ class AteImageBubbleMenuComponent extends AteBaseBubbleMenu {
3438
3582
  }
3439
3583
  getSelectionRect() {
3440
3584
  const ed = this.editor();
3441
- if (!ed)
3585
+ if (!ed) {
3442
3586
  return new DOMRect(0, 0, 0, 0);
3587
+ }
3443
3588
  const { from } = ed.state.selection;
3444
3589
  try {
3445
3590
  // 1. Direct ProseMirror approach: get DOM node at position
@@ -3448,8 +3593,9 @@ class AteImageBubbleMenuComponent extends AteBaseBubbleMenu {
3448
3593
  // If it's a resizable container, look for the image inside
3449
3594
  if (dom.classList.contains("resizable-image-container")) {
3450
3595
  const img = dom.querySelector("img");
3451
- if (img)
3596
+ if (img) {
3452
3597
  return img.getBoundingClientRect();
3598
+ }
3453
3599
  }
3454
3600
  return dom.getBoundingClientRect();
3455
3601
  }
@@ -3489,8 +3635,9 @@ class AteImageBubbleMenuComponent extends AteBaseBubbleMenu {
3489
3635
  }
3490
3636
  async changeImage() {
3491
3637
  const ed = this.editor();
3492
- if (!ed)
3638
+ if (!ed) {
3493
3639
  return;
3640
+ }
3494
3641
  try {
3495
3642
  // Use dedicated method to replace an existing image
3496
3643
  await this.imageService.selectAndReplaceImage(ed, this.imageUpload());
@@ -3653,8 +3800,9 @@ class AteTableBubbleMenuComponent extends AteBaseBubbleMenu {
3653
3800
  }
3654
3801
  getSelectionRect() {
3655
3802
  const ed = this.editor();
3656
- if (!ed)
3803
+ if (!ed) {
3657
3804
  return new DOMRect(0, 0, 0, 0);
3805
+ }
3658
3806
  const { from } = ed.state.selection;
3659
3807
  try {
3660
3808
  // 1. Direct ProseMirror approach: get DOM node at position
@@ -3674,8 +3822,9 @@ class AteTableBubbleMenuComponent extends AteBaseBubbleMenu {
3674
3822
  // Search for table element at these coordinates
3675
3823
  const element = document.elementFromPoint(coords.left, coords.top);
3676
3824
  const table = element?.closest("table");
3677
- if (table)
3825
+ if (table) {
3678
3826
  return table.getBoundingClientRect();
3827
+ }
3679
3828
  }
3680
3829
  // 3. Ultimate fallback if selection is ambiguous
3681
3830
  const activeTable = ed.view.dom.querySelector("table.selected, table:has(.selected), table:has(.selected-cell), table:has(.selected-node)");
@@ -3893,8 +4042,9 @@ class AteCellBubbleMenuComponent extends AteBaseBubbleMenu {
3893
4042
  }
3894
4043
  getSelectionRect() {
3895
4044
  const ed = this.editor();
3896
- if (!ed)
4045
+ if (!ed) {
3897
4046
  return new DOMRect(0, 0, 0, 0);
4047
+ }
3898
4048
  // CellSelection
3899
4049
  const selection = ed.state.selection;
3900
4050
  // 1. Multiple cells selected
@@ -3902,8 +4052,9 @@ class AteCellBubbleMenuComponent extends AteBaseBubbleMenu {
3902
4052
  const cells = [];
3903
4053
  // Try to find all selected cell nodes
3904
4054
  ed.view.dom.querySelectorAll(".selectedCell").forEach(el => {
3905
- if (el instanceof HTMLElement)
4055
+ if (el instanceof HTMLElement) {
3906
4056
  cells.push(el);
4057
+ }
3907
4058
  });
3908
4059
  if (cells.length > 0) {
3909
4060
  let top = Infinity, bottom = -Infinity, left = Infinity, right = -Infinity;
@@ -3989,21 +4140,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
3989
4140
  }]
3990
4141
  }], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }] } });
3991
4142
 
3992
- class AteLinkBubbleMenuComponent {
4143
+ /**
4144
+ * Base abstract class for Sub-Bubble Menus (Link, Color).
4145
+ * These menus are "sub-menus" of the main bubble menu or triggered/anchored by specific UI elements.
4146
+ */
4147
+ class AteBaseSubBubbleMenu {
3993
4148
  constructor() {
3994
4149
  this.i18nService = inject(AteI18nService);
3995
4150
  this.editorCommands = inject(AteEditorCommandsService);
3996
- this.linkSvc = inject(AteLinkService);
3997
- this.t = this.i18nService.bubbleMenu;
3998
- this.common = this.i18nService.common;
3999
- this.state = this.editorCommands.editorState;
4151
+ // Core Inputs
4000
4152
  this.editor = input.required(...(ngDevMode ? [{ debugName: "editor" }] : []));
4153
+ // Required viewChild for the menu container
4154
+ this.menuRef = viewChild.required("menuRef");
4155
+ // Internal State
4001
4156
  this.tippyInstance = null;
4002
4157
  this.updateTimeout = null;
4003
- this.editUrl = signal("", ...(ngDevMode ? [{ debugName: "editUrl" }] : []));
4158
+ // Reactive state alias for templates
4159
+ this.state = this.editorCommands.editorState;
4160
+ /**
4161
+ * Core logic to decide whether to show or hide the menu.
4162
+ */
4004
4163
  this.updateMenu = () => {
4005
- if (this.updateTimeout)
4164
+ if (this.updateTimeout) {
4006
4165
  clearTimeout(this.updateTimeout);
4166
+ }
4007
4167
  this.updateTimeout = setTimeout(() => {
4008
4168
  if (this.shouldShow()) {
4009
4169
  this.showTippy();
@@ -4013,24 +4173,11 @@ class AteLinkBubbleMenuComponent {
4013
4173
  }
4014
4174
  }, 10);
4015
4175
  };
4016
- // Reactive effect for URL sync and focus
4017
- effect(() => {
4018
- const state = this.state();
4019
- const isInteracting = this.linkSvc.isInteracting();
4020
- const currentLinkHref = state.marks.linkHref || "";
4021
- // SYNC LOGIC:
4022
- // If we are NOT currently typing (interacting),
4023
- // always keep the input in sync with the current editor selection.
4024
- if (!isInteracting) {
4025
- this.editUrl.set(currentLinkHref);
4026
- }
4027
- });
4028
4176
  // Reactive effect for menu updates (re-positioning)
4029
4177
  effect(() => {
4178
+ // Monitor editor state and specific sub-menu states
4030
4179
  this.state();
4031
- this.linkSvc.editMode();
4032
- this.linkSvc.menuTrigger();
4033
- this.linkSvc.isInteracting();
4180
+ this.onStateChange();
4034
4181
  this.updateMenu();
4035
4182
  });
4036
4183
  }
@@ -4038,21 +4185,28 @@ class AteLinkBubbleMenuComponent {
4038
4185
  this.initTippy();
4039
4186
  }
4040
4187
  ngOnDestroy() {
4041
- if (this.updateTimeout)
4188
+ if (this.updateTimeout) {
4042
4189
  clearTimeout(this.updateTimeout);
4190
+ }
4043
4191
  if (this.tippyInstance) {
4044
4192
  this.tippyInstance.destroy();
4045
4193
  this.tippyInstance = null;
4046
4194
  }
4047
4195
  }
4196
+ /**
4197
+ * Initializes the Tippy instance with sub-menu defaults.
4198
+ */
4048
4199
  initTippy() {
4049
- if (!this.menuRef?.nativeElement) {
4200
+ if (!this.menuRef()?.nativeElement) {
4050
4201
  setTimeout(() => this.initTippy(), 50);
4051
4202
  return;
4052
4203
  }
4053
4204
  const ed = this.editor();
4205
+ if (this.tippyInstance) {
4206
+ this.tippyInstance.destroy();
4207
+ }
4054
4208
  this.tippyInstance = tippy(document.body, {
4055
- content: this.menuRef.nativeElement,
4209
+ content: this.menuRef().nativeElement,
4056
4210
  trigger: "manual",
4057
4211
  placement: "bottom-start",
4058
4212
  appendTo: () => ed.options.element,
@@ -4075,41 +4229,76 @@ class AteLinkBubbleMenuComponent {
4075
4229
  },
4076
4230
  ],
4077
4231
  },
4078
- onShow: () => {
4079
- // We no longer auto-focus the input here to keep the editor selection visible.
4080
- // The user can click the input if they want to type, but for swatches/preview,
4081
- // staying in the editor is better for UX.
4082
- },
4083
- onHide: () => {
4084
- // Clear trigger only AFTER the menu is hidden to maintain anchor stability during animation
4085
- this.linkSvc.done();
4086
- this.linkSvc.close();
4087
- },
4232
+ onShow: instance => this.onTippyShow(instance),
4233
+ onHide: instance => this.onTippyHide(instance),
4088
4234
  });
4089
4235
  this.updateMenu();
4090
4236
  }
4237
+ /**
4238
+ * Helper to show the Tippy instance with updated positioning.
4239
+ */
4091
4240
  showTippy() {
4092
4241
  if (this.tippyInstance) {
4093
4242
  this.tippyInstance.setProps({ getReferenceClientRect: () => this.getSelectionRect() });
4094
4243
  this.tippyInstance.show();
4095
4244
  }
4096
4245
  }
4246
+ /**
4247
+ * Helper to hide the Tippy instance.
4248
+ */
4097
4249
  hideTippy() {
4098
4250
  this.tippyInstance?.hide();
4099
4251
  }
4100
- focusInput() {
4101
- setTimeout(() => {
4102
- this.linkInput?.nativeElement?.focus();
4103
- this.linkInput?.nativeElement?.select();
4104
- }, 50);
4252
+ /**
4253
+ * Optional hook called when Tippy is about to be shown.
4254
+ */
4255
+ onTippyShow(_instance) {
4256
+ /* empty */
4105
4257
  }
4106
- currentUrl() {
4107
- return this.state().marks.linkHref || "";
4258
+ /**
4259
+ * Optional hook called when Tippy is about to be hidden.
4260
+ */
4261
+ onTippyHide(_instance) {
4262
+ /* empty */
4263
+ }
4264
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AteBaseSubBubbleMenu, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
4265
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "20.3.16", type: AteBaseSubBubbleMenu, isStandalone: true, inputs: { editor: { classPropertyName: "editor", publicName: "editor", isSignal: true, isRequired: true, transformFunction: null } }, viewQueries: [{ propertyName: "menuRef", first: true, predicate: ["menuRef"], descendants: true, isSignal: true }], ngImport: i0 }); }
4266
+ }
4267
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AteBaseSubBubbleMenu, decorators: [{
4268
+ type: Directive
4269
+ }], ctorParameters: () => [], propDecorators: { editor: [{ type: i0.Input, args: [{ isSignal: true, alias: "editor", required: true }] }], menuRef: [{ type: i0.ViewChild, args: ["menuRef", { isSignal: true }] }] } });
4270
+
4271
+ class AteLinkBubbleMenuComponent extends AteBaseSubBubbleMenu {
4272
+ constructor() {
4273
+ super();
4274
+ this.linkSvc = inject(AteLinkService);
4275
+ this.t = this.i18nService.bubbleMenu;
4276
+ this.common = this.i18nService.common;
4277
+ this.linkInput = viewChild("linkInput", ...(ngDevMode ? [{ debugName: "linkInput" }] : []));
4278
+ this.editUrl = signal("", ...(ngDevMode ? [{ debugName: "editUrl" }] : []));
4279
+ // Reactive effect for URL sync
4280
+ effect(() => {
4281
+ const state = this.state();
4282
+ const isInteracting = this.linkSvc.isInteracting();
4283
+ const currentLinkHref = state.marks.linkHref || "";
4284
+ // SYNC LOGIC:
4285
+ // If we are NOT currently typing (interacting),
4286
+ // always keep the input in sync with the current editor selection.
4287
+ if (!isInteracting) {
4288
+ this.editUrl.set(currentLinkHref);
4289
+ }
4290
+ });
4291
+ }
4292
+ onStateChange() {
4293
+ this.linkSvc.editMode();
4294
+ this.linkSvc.menuTrigger();
4295
+ this.linkSvc.isInteracting();
4108
4296
  }
4109
4297
  shouldShow() {
4110
4298
  const { selection, marks, isEditable, isFocused } = this.state();
4111
- if (!isEditable)
4299
+ if (!isEditable) {
4112
4300
  return false;
4301
+ }
4113
4302
  // Show if explicitly in edit mode (from toolbar/bubble menu) or interacting with input
4114
4303
  if (this.linkSvc.editMode() || this.linkSvc.isInteracting()) {
4115
4304
  return true;
@@ -4120,14 +4309,16 @@ class AteLinkBubbleMenuComponent {
4120
4309
  getSelectionRect() {
4121
4310
  const trigger = this.linkSvc.menuTrigger();
4122
4311
  const ed = this.editor();
4123
- if (!ed)
4312
+ if (!ed) {
4124
4313
  return new DOMRect(0, 0, 0, 0);
4314
+ }
4125
4315
  // 1. If we have a stable trigger from service (toolbar or parent menu), anchor to it
4126
4316
  if (trigger) {
4127
4317
  const rect = trigger.getBoundingClientRect();
4128
4318
  // Only use if it's still visible/in DOM (width > 0)
4129
- if (rect.width > 0)
4319
+ if (rect.width > 0) {
4130
4320
  return rect;
4321
+ }
4131
4322
  }
4132
4323
  // 2. Otherwise (bubble menu / relay), anchor to text selection
4133
4324
  const { from } = ed.state.selection;
@@ -4135,8 +4326,9 @@ class AteLinkBubbleMenuComponent {
4135
4326
  const { node } = ed.view.domAtPos(from);
4136
4327
  const element = node instanceof Element ? node : node.parentElement;
4137
4328
  const linkElement = element?.closest("a");
4138
- if (linkElement)
4329
+ if (linkElement) {
4139
4330
  return linkElement.getBoundingClientRect();
4331
+ }
4140
4332
  }
4141
4333
  catch (_e) {
4142
4334
  /* ignore */
@@ -4146,13 +4338,22 @@ class AteLinkBubbleMenuComponent {
4146
4338
  if (selection && selection.rangeCount > 0) {
4147
4339
  const range = selection.getRangeAt(0);
4148
4340
  const rect = range.getBoundingClientRect();
4149
- if (rect.width > 0 && rect.height > 0)
4341
+ if (rect.width > 0 && rect.height > 0) {
4150
4342
  return rect;
4343
+ }
4151
4344
  }
4152
4345
  // Final fallback to coordinates at cursor
4153
4346
  const { top, bottom, left, right } = ed.view.coordsAtPos(from);
4154
4347
  return new DOMRect(left, top, right - left, bottom - top);
4155
4348
  }
4349
+ onTippyHide() {
4350
+ // Clear trigger only AFTER the menu is hidden to maintain anchor stability during animation
4351
+ this.linkSvc.done();
4352
+ this.linkSvc.close();
4353
+ }
4354
+ currentUrl() {
4355
+ return this.state().marks.linkHref || "";
4356
+ }
4156
4357
  onMouseDown(event) {
4157
4358
  event.stopPropagation();
4158
4359
  const target = event.target;
@@ -4173,8 +4374,9 @@ class AteLinkBubbleMenuComponent {
4173
4374
  event.preventDefault();
4174
4375
  event.stopPropagation();
4175
4376
  const url = this.currentUrl();
4176
- if (url)
4377
+ if (url) {
4177
4378
  window.open(url, "_blank", "noopener,noreferrer");
4379
+ }
4178
4380
  }
4179
4381
  onRemove(event) {
4180
4382
  event.preventDefault();
@@ -4202,7 +4404,7 @@ class AteLinkBubbleMenuComponent {
4202
4404
  this.hideTippy();
4203
4405
  }
4204
4406
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AteLinkBubbleMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4205
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.16", type: AteLinkBubbleMenuComponent, isStandalone: true, selector: "ate-link-bubble-menu", inputs: { editor: { classPropertyName: "editor", publicName: "editor", isSignal: true, isRequired: true, transformFunction: null } }, viewQueries: [{ propertyName: "linkInput", first: true, predicate: ["linkInput"], descendants: true }, { propertyName: "menuRef", first: true, predicate: ["menuRef"], descendants: true }], ngImport: i0, template: `
4407
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.3.16", type: AteLinkBubbleMenuComponent, isStandalone: true, selector: "ate-link-bubble-menu", viewQueries: [{ propertyName: "linkInput", first: true, predicate: ["linkInput"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
4206
4408
  <div
4207
4409
  #menuRef
4208
4410
  class="bubble-menu"
@@ -4303,13 +4505,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
4303
4505
  </div>
4304
4506
  </div>
4305
4507
  `, styles: [".link-input-row{display:flex;align-items:center;gap:6px}.url-input-container{flex:1;display:flex;align-items:center;background:var(--ate-surface-secondary, #f8fafc);border:1px solid var(--ate-border, #e2e8f0);border-radius:8px;padding:0 10px;height:32px;transition:all .15s ease}.url-input-container:focus-within{border-color:var(--ate-primary, #3b82f6);background:var(--ate-surface, #ffffff);box-shadow:0 0 0 2px var(--ate-primary-light, rgba(59, 130, 246, .1))}.icon-link{font-size:16px;color:var(--ate-text-muted, #94a3b8);margin-right:6px}.url-field{background:transparent;border:none;outline:none;color:var(--ate-text, #1e293b);font-size:13px;width:100%;font-family:inherit}.action-buttons{display:flex;align-items:center;gap:2px}\n"] }]
4306
- }], ctorParameters: () => [], propDecorators: { editor: [{ type: i0.Input, args: [{ isSignal: true, alias: "editor", required: true }] }], linkInput: [{
4307
- type: ViewChild,
4308
- args: ["linkInput"]
4309
- }], menuRef: [{
4310
- type: ViewChild,
4311
- args: ["menuRef", { static: false }]
4312
- }] } });
4508
+ }], ctorParameters: () => [], propDecorators: { linkInput: [{ type: i0.ViewChild, args: ["linkInput", { isSignal: true }] }] } });
4313
4509
 
4314
4510
  const PRESET_COLORS = [
4315
4511
  "#000000",
@@ -4325,36 +4521,19 @@ const PRESET_COLORS = [
4325
4521
  "#9C27B0",
4326
4522
  "#E91E63",
4327
4523
  ];
4328
- class AteColorBubbleMenuComponent {
4524
+ class AteColorBubbleMenuComponent extends AteBaseSubBubbleMenu {
4329
4525
  constructor() {
4330
- this.i18nService = inject(AteI18nService);
4331
- this.editorCommands = inject(AteEditorCommandsService);
4526
+ super(...arguments);
4332
4527
  this.colorPickerSvc = inject(AteColorPickerService);
4333
4528
  this.t = this.i18nService.toolbar;
4334
4529
  this.common = this.i18nService.common;
4335
- this.state = this.editorCommands.editorState;
4336
4530
  this.presets = PRESET_COLORS;
4337
- this.editor = input.required(...(ngDevMode ? [{ debugName: "editor" }] : []));
4338
4531
  this.colorInputRef = viewChild("colorInput", ...(ngDevMode ? [{ debugName: "colorInputRef" }] : []));
4339
- this.tippyInstance = null;
4340
- this.updateTimeout = null;
4341
4532
  /**
4342
4533
  * LOCAL MODE: We lock the mode when the menu is shown to avoid race conditions
4343
4534
  * where the parent (bubble menu) clears the global signal before we apply the color.
4344
4535
  */
4345
4536
  this.activeMode = signal("text", ...(ngDevMode ? [{ debugName: "activeMode" }] : []));
4346
- this.updateMenu = () => {
4347
- if (this.updateTimeout)
4348
- clearTimeout(this.updateTimeout);
4349
- this.updateTimeout = setTimeout(() => {
4350
- if (this.shouldShow()) {
4351
- this.showTippy();
4352
- }
4353
- else {
4354
- this.hideTippy();
4355
- }
4356
- }, 10);
4357
- };
4358
4537
  this.currentColor = computed(() => {
4359
4538
  const marks = this.state().marks;
4360
4539
  const color = this.activeMode() === "text" ? marks.computedColor : marks.computedBackground;
@@ -4364,85 +4543,17 @@ class AteColorBubbleMenuComponent {
4364
4543
  const color = this.currentColor();
4365
4544
  return color.replace("#", "").toUpperCase();
4366
4545
  }, ...(ngDevMode ? [{ debugName: "hexValue" }] : []));
4367
- effect(() => {
4368
- this.state();
4369
- this.colorPickerSvc.editMode();
4370
- this.colorPickerSvc.menuTrigger();
4371
- this.colorPickerSvc.isInteracting();
4372
- this.updateMenu();
4373
- });
4374
- }
4375
- ngOnInit() {
4376
- this.initTippy();
4377
- }
4378
- ngOnDestroy() {
4379
- if (this.updateTimeout)
4380
- clearTimeout(this.updateTimeout);
4381
- if (this.tippyInstance) {
4382
- this.tippyInstance.destroy();
4383
- this.tippyInstance = null;
4384
- }
4385
- }
4386
- initTippy() {
4387
- if (!this.menuRef?.nativeElement) {
4388
- setTimeout(() => this.initTippy(), 50);
4389
- return;
4390
- }
4391
- const ed = this.editor();
4392
- this.tippyInstance = tippy(document.body, {
4393
- content: this.menuRef.nativeElement,
4394
- trigger: "manual",
4395
- placement: "bottom-start",
4396
- appendTo: () => ed.options.element,
4397
- interactive: true,
4398
- arrow: false,
4399
- offset: [0, 8],
4400
- hideOnClick: true,
4401
- plugins: [sticky],
4402
- sticky: false,
4403
- getReferenceClientRect: () => this.getSelectionRect(),
4404
- popperOptions: {
4405
- modifiers: [
4406
- {
4407
- name: "preventOverflow",
4408
- options: { boundary: ed.options.element, padding: 8 },
4409
- },
4410
- {
4411
- name: "flip",
4412
- options: { fallbackPlacements: ["top-start", "bottom-end", "top-end"] },
4413
- },
4414
- ],
4415
- },
4416
- onShow: () => {
4417
- // 1. Lock the mode immediately to be immune to external signal changes
4418
- const currentMode = this.colorPickerSvc.editMode() || "text";
4419
- this.activeMode.set(currentMode);
4420
- // 2. Capture selection for the command fallback
4421
- this.colorPickerSvc.captureSelection(this.editor());
4422
- // Note: We don't auto-focus the Hex input anymore to keep the
4423
- // visual selection (blue highlight) active in the editor.
4424
- },
4425
- onHide: () => {
4426
- // Clear trigger only AFTER the menu is hidden to maintain anchor stability during animation
4427
- this.colorPickerSvc.done();
4428
- this.colorPickerSvc.close();
4429
- },
4430
- });
4431
- this.updateMenu();
4432
4546
  }
4433
- showTippy() {
4434
- if (this.tippyInstance) {
4435
- this.tippyInstance.setProps({ getReferenceClientRect: () => this.getSelectionRect() });
4436
- this.tippyInstance.show();
4437
- }
4438
- }
4439
- hideTippy() {
4440
- this.tippyInstance?.hide();
4547
+ onStateChange() {
4548
+ this.colorPickerSvc.editMode();
4549
+ this.colorPickerSvc.menuTrigger();
4550
+ this.colorPickerSvc.isInteracting();
4441
4551
  }
4442
4552
  shouldShow() {
4443
4553
  const { isEditable } = this.state();
4444
- if (!isEditable)
4554
+ if (!isEditable) {
4445
4555
  return false;
4556
+ }
4446
4557
  if (this.colorPickerSvc.editMode() !== null || this.colorPickerSvc.isInteracting()) {
4447
4558
  return true;
4448
4559
  }
@@ -4451,14 +4562,16 @@ class AteColorBubbleMenuComponent {
4451
4562
  getSelectionRect() {
4452
4563
  const trigger = this.colorPickerSvc.menuTrigger();
4453
4564
  const ed = this.editor();
4454
- if (!ed)
4565
+ if (!ed) {
4455
4566
  return new DOMRect(0, 0, 0, 0);
4567
+ }
4456
4568
  // 1. If we have a stable trigger from service (toolbar or parent menu), anchor to it
4457
4569
  if (trigger) {
4458
4570
  const rect = trigger.getBoundingClientRect();
4459
4571
  // Only use if it's still visible/in DOM (width > 0)
4460
- if (rect.width > 0)
4572
+ if (rect.width > 0) {
4461
4573
  return rect;
4574
+ }
4462
4575
  }
4463
4576
  // 2. Otherwise (bubble menu / relay), anchor to text selection
4464
4577
  const { from } = ed.state.selection;
@@ -4466,8 +4579,9 @@ class AteColorBubbleMenuComponent {
4466
4579
  const { node } = ed.view.domAtPos(from);
4467
4580
  const element = node instanceof Element ? node : node.parentElement;
4468
4581
  const colorElement = element?.closest('[style*="color"], [style*="background"], mark');
4469
- if (colorElement)
4582
+ if (colorElement) {
4470
4583
  return colorElement.getBoundingClientRect();
4584
+ }
4471
4585
  }
4472
4586
  catch (_e) {
4473
4587
  /* ignore */
@@ -4477,15 +4591,29 @@ class AteColorBubbleMenuComponent {
4477
4591
  if (selection && selection.rangeCount > 0) {
4478
4592
  const range = selection.getRangeAt(0);
4479
4593
  const rect = range.getBoundingClientRect();
4480
- if (rect.width > 0 && rect.height > 0)
4594
+ if (rect.width > 0 && rect.height > 0) {
4481
4595
  return rect;
4596
+ }
4482
4597
  }
4483
4598
  // Final fallback to coordinates at cursor
4484
4599
  const { top, bottom, left, right } = ed.view.coordsAtPos(from);
4485
4600
  return new DOMRect(left, top, right - left, bottom - top);
4486
4601
  }
4602
+ onTippyShow(_instance) {
4603
+ // 1. Lock the mode immediately to be immune to external signal changes
4604
+ const currentMode = this.colorPickerSvc.editMode() || "text";
4605
+ this.activeMode.set(currentMode);
4606
+ // 2. Capture selection for the command fallback
4607
+ this.colorPickerSvc.captureSelection(this.editor());
4608
+ }
4609
+ onTippyHide(_instance) {
4610
+ // Clear trigger only AFTER the menu is hidden to maintain anchor stability during animation
4611
+ this.colorPickerSvc.done();
4612
+ this.colorPickerSvc.close();
4613
+ }
4487
4614
  isColorActive(color) {
4488
- return this.colorPickerSvc.normalizeColor(this.currentColor()) === this.colorPickerSvc.normalizeColor(color);
4615
+ return (this.colorPickerSvc.normalizeColor(this.currentColor()) ===
4616
+ this.colorPickerSvc.normalizeColor(color));
4489
4617
  }
4490
4618
  applyColor(color, addToHistory = true, event) {
4491
4619
  if (event) {
@@ -4513,8 +4641,9 @@ class AteColorBubbleMenuComponent {
4513
4641
  onHexInput(event) {
4514
4642
  const input = event.target;
4515
4643
  let value = input.value.trim();
4516
- if (!value.startsWith("#"))
4644
+ if (!value.startsWith("#")) {
4517
4645
  value = "#" + value;
4646
+ }
4518
4647
  if (/^#?[0-9A-Fa-f]{3,6}$/.test(value)) {
4519
4648
  this.applyColor(value, false);
4520
4649
  }
@@ -4522,8 +4651,9 @@ class AteColorBubbleMenuComponent {
4522
4651
  onHexChange(event) {
4523
4652
  const input = event.target;
4524
4653
  let value = input.value.trim();
4525
- if (!value.startsWith("#"))
4654
+ if (!value.startsWith("#")) {
4526
4655
  value = "#" + value;
4656
+ }
4527
4657
  if (/^#?[0-9A-Fa-f]{3,6}$/.test(value)) {
4528
4658
  this.applyColor(value, true, event);
4529
4659
  }
@@ -4570,8 +4700,8 @@ class AteColorBubbleMenuComponent {
4570
4700
  this.updateMenu();
4571
4701
  }, 150);
4572
4702
  }
4573
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AteColorBubbleMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4574
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: AteColorBubbleMenuComponent, isStandalone: true, selector: "ate-color-bubble-menu", inputs: { editor: { classPropertyName: "editor", publicName: "editor", isSignal: true, isRequired: true, transformFunction: null } }, viewQueries: [{ propertyName: "colorInputRef", first: true, predicate: ["colorInput"], descendants: true, isSignal: true }, { propertyName: "menuRef", first: true, predicate: ["menuRef"], descendants: true }], ngImport: i0, template: `
4703
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AteColorBubbleMenuComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
4704
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: AteColorBubbleMenuComponent, isStandalone: true, selector: "ate-color-bubble-menu", viewQueries: [{ propertyName: "colorInputRef", first: true, predicate: ["colorInput"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
4575
4705
  <div
4576
4706
  #menuRef
4577
4707
  class="bubble-menu color-bubble-menu"
@@ -4732,10 +4862,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
4732
4862
  </div>
4733
4863
  </div>
4734
4864
  `, styles: [".color-picker-container{display:flex;flex-direction:column;gap:8px}.dropdown-row{display:flex;align-items:center;width:100%}.dropdown-row.presets{justify-content:center}.dropdown-row.controls{gap:8px;justify-content:space-between;padding-top:4px;border-top:1px solid var(--ate-border, #e2e8f0)}.color-grid{display:grid;grid-template-columns:repeat(12,1fr);gap:4px;width:100%}:host ::ng-deep .color-swatch-btn .ate-button{width:100%;aspect-ratio:1;height:auto;border-radius:4px;border:1px solid rgba(0,0,0,.1);padding:0}:host ::ng-deep .color-swatch-btn .ate-button.is-active{border-color:var(--ate-primary, #3b82f6);box-shadow:0 0 0 2px #3b82f64d}:host ::ng-deep .btn-native-picker-trigger .ate-button{color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.2)}.divider-v{width:1px;height:24px;background:var(--ate-border, #e2e8f0)}.hex-input-wrapper{flex:1;display:flex;align-items:center;background:var(--ate-surface-secondary, #f8fafc);border:1px solid var(--ate-border, #e2e8f0);border-radius:8px;padding:0 10px;height:32px;transition:border-color .15s ease}.hex-input-wrapper:focus-within{border-color:var(--ate-primary, #3b82f6);background:var(--ate-menu-bg, #ffffff)}.hex-hash{color:var(--ate-text-muted, #94a3b8);font-family:monospace;font-size:.875rem}.hex-input{background:transparent;border:none;outline:none;color:var(--ate-text, #1e293b);font-family:monospace;font-size:.875rem;width:100%;padding-left:4px}.native-trigger-wrapper{position:relative;width:32px;height:32px}.hidden-native-input{position:absolute;inset:0;opacity:0;width:100%;height:100%;cursor:pointer}\n"] }]
4735
- }], ctorParameters: () => [], propDecorators: { editor: [{ type: i0.Input, args: [{ isSignal: true, alias: "editor", required: true }] }], menuRef: [{
4736
- type: ViewChild,
4737
- args: ["menuRef", { static: false }]
4738
- }], colorInputRef: [{ type: i0.ViewChild, args: ["colorInput", { isSignal: true }] }] } });
4865
+ }], propDecorators: { colorInputRef: [{ type: i0.ViewChild, args: ["colorInput", { isSignal: true }] }] } });
4739
4866
 
4740
4867
  /**
4741
4868
  * Clés des commandes natives dans l'ordre d'affichage
@@ -4873,6 +5000,7 @@ class AteSlashCommandsComponent {
4873
5000
  this.i18nService = inject(AteI18nService);
4874
5001
  this.editor = input.required(...(ngDevMode ? [{ debugName: "editor" }] : []));
4875
5002
  this.config = input(undefined, ...(ngDevMode ? [{ debugName: "config" }] : []));
5003
+ this.menuRef = viewChild.required("menuRef");
4876
5004
  this.tippyInstance = null;
4877
5005
  this.editorCommands = inject(AteEditorCommandsService);
4878
5006
  // Local state
@@ -4903,8 +5031,9 @@ class AteSlashCommandsComponent {
4903
5031
  }, ...(ngDevMode ? [{ debugName: "filteredCommands" }] : []));
4904
5032
  this.updateMenu = () => {
4905
5033
  const ed = this.editor();
4906
- if (!ed)
5034
+ if (!ed) {
4907
5035
  return;
5036
+ }
4908
5037
  const { from } = ed.state.selection;
4909
5038
  // Check if '/' was typed at the beginning of a line or after a space
4910
5039
  const textBefore = ed.state.doc.textBetween(Math.max(0, from - 20), from, "\n");
@@ -4937,57 +5066,11 @@ class AteSlashCommandsComponent {
4937
5066
  this.handleBlur = () => {
4938
5067
  setTimeout(() => this.hideTippy(), 100);
4939
5068
  };
4940
- this.handleKeyDown = (event) => {
4941
- // Only handle keys if menu is active
4942
- if (!this.isActive || this.filteredCommands().length === 0) {
4943
- return;
4944
- }
4945
- switch (event.key) {
4946
- case "ArrowDown": {
4947
- event.preventDefault();
4948
- event.stopPropagation();
4949
- const nextIndex = (this.selectedIndex() + 1) % this.filteredCommands().length;
4950
- this.selectedIndex.set(nextIndex);
4951
- this.scrollToSelected();
4952
- break;
4953
- }
4954
- case "ArrowUp": {
4955
- event.preventDefault();
4956
- event.stopPropagation();
4957
- const prevIndex = this.selectedIndex() === 0 ? this.filteredCommands().length - 1 : this.selectedIndex() - 1;
4958
- this.selectedIndex.set(prevIndex);
4959
- this.scrollToSelected();
4960
- break;
4961
- }
4962
- case "Enter": {
4963
- event.preventDefault();
4964
- event.stopPropagation();
4965
- const selectedCommand = this.filteredCommands()[this.selectedIndex()];
4966
- if (selectedCommand) {
4967
- this.executeCommand(selectedCommand);
4968
- }
4969
- break;
4970
- }
4971
- case "Escape": {
4972
- event.preventDefault();
4973
- event.stopPropagation();
4974
- this.isActive = false;
4975
- this.hideTippy();
4976
- // Optional: remove the typed "/"
4977
- const ed = this.editor();
4978
- if (ed && this.slashRange) {
4979
- const { tr } = ed.state;
4980
- tr.delete(this.slashRange.from, this.slashRange.to);
4981
- ed.view.dispatch(tr);
4982
- }
4983
- break;
4984
- }
4985
- }
4986
- };
4987
5069
  effect(() => {
4988
5070
  const ed = this.editor();
4989
- if (!ed)
5071
+ if (!ed) {
4990
5072
  return;
5073
+ }
4991
5074
  // Clean up old listeners
4992
5075
  ed.off("selectionUpdate", this.updateMenu);
4993
5076
  ed.off("transaction", this.updateMenu);
@@ -5020,11 +5103,11 @@ class AteSlashCommandsComponent {
5020
5103
  }
5021
5104
  }
5022
5105
  initTippy() {
5023
- if (!this.menuRef?.nativeElement) {
5106
+ if (!this.menuRef()?.nativeElement) {
5024
5107
  setTimeout(() => this.initTippy(), 50);
5025
5108
  return;
5026
5109
  }
5027
- const menuElement = this.menuRef.nativeElement;
5110
+ const menuElement = this.menuRef().nativeElement;
5028
5111
  if (this.tippyInstance) {
5029
5112
  this.tippyInstance.destroy();
5030
5113
  }
@@ -5093,8 +5176,9 @@ class AteSlashCommandsComponent {
5093
5176
  }
5094
5177
  scrollToSelected() {
5095
5178
  // Scroll to the selected element
5096
- if (this.menuRef?.nativeElement) {
5097
- const selectedItem = this.menuRef.nativeElement.querySelector(".slash-command-item.selected");
5179
+ const menuEl = this.menuRef()?.nativeElement;
5180
+ if (menuEl) {
5181
+ const selectedItem = menuEl.querySelector(".slash-command-item.selected");
5098
5182
  if (selectedItem) {
5099
5183
  selectedItem.scrollIntoView({ block: "nearest", behavior: "smooth" });
5100
5184
  }
@@ -5114,8 +5198,9 @@ class AteSlashCommandsComponent {
5114
5198
  }
5115
5199
  executeCommand(command) {
5116
5200
  const ed = this.editor();
5117
- if (!ed || !this.slashRange)
5201
+ if (!ed || !this.slashRange) {
5118
5202
  return;
5203
+ }
5119
5204
  // Remove slash text ("/")
5120
5205
  const { tr } = ed.state;
5121
5206
  tr.delete(this.slashRange.from, this.slashRange.to);
@@ -5150,7 +5235,9 @@ class AteSlashCommandsComponent {
5150
5235
  }
5151
5236
  case "ArrowUp": {
5152
5237
  event.preventDefault();
5153
- const prevIndex = this.selectedIndex() === 0 ? this.filteredCommands().length - 1 : this.selectedIndex() - 1;
5238
+ const prevIndex = this.selectedIndex() === 0
5239
+ ? this.filteredCommands().length - 1
5240
+ : this.selectedIndex() - 1;
5154
5241
  this.selectedIndex.set(prevIndex);
5155
5242
  this.scrollToSelected();
5156
5243
  return true;
@@ -5186,7 +5273,7 @@ class AteSlashCommandsComponent {
5186
5273
  }));
5187
5274
  }
5188
5275
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AteSlashCommandsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
5189
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: AteSlashCommandsComponent, isStandalone: true, selector: "ate-slash-commands", inputs: { editor: { classPropertyName: "editor", publicName: "editor", isSignal: true, isRequired: true, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "menuRef", first: true, predicate: ["menuRef"], descendants: true }], ngImport: i0, template: `
5276
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: AteSlashCommandsComponent, isStandalone: true, selector: "ate-slash-commands", inputs: { editor: { classPropertyName: "editor", publicName: "editor", isSignal: true, isRequired: true, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "menuRef", first: true, predicate: ["menuRef"], descendants: true, isSignal: true }], ngImport: i0, template: `
5190
5277
  <div #menuRef class="slash-commands-menu">
5191
5278
  @for (command of filteredCommands(); track command.title) {
5192
5279
  <div
@@ -5227,10 +5314,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
5227
5314
  }
5228
5315
  </div>
5229
5316
  `, styles: [".slash-commands-menu{background:var(--ate-menu-bg);border:1px solid var(--ate-menu-border);border-radius:var(--ate-menu-border-radius, 12px);box-shadow:var(--ate-menu-shadow);padding:var(--ate-menu-padding);max-height:320px;overflow-y:auto;min-width:280px;outline:none;animation:slashMenuFadeIn .2s cubic-bezier(0,0,.2,1);scrollbar-width:thin;scrollbar-color:var(--ate-scrollbar-thumb) var(--ate-scrollbar-track)}.slash-commands-menu::-webkit-scrollbar{width:var(--ate-scrollbar-width)}.slash-commands-menu::-webkit-scrollbar-track{background:var(--ate-scrollbar-track)}.slash-commands-menu::-webkit-scrollbar-thumb{background:var(--ate-scrollbar-thumb);border:3px solid transparent;background-clip:content-box;border-radius:10px}.slash-commands-menu::-webkit-scrollbar-thumb:hover{background:var(--ate-scrollbar-thumb-hover);background-clip:content-box}@keyframes slashMenuFadeIn{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.slash-command-item{display:flex;align-items:center;gap:12px;padding:var(--ate-menu-padding);border-radius:var(--ate-menu-border-radius, 8px);cursor:pointer;transition:all .15s ease;border:var(--ate-border-width, 1px) solid transparent;outline:none;margin-bottom:2px}.slash-command-item:last-child{margin-bottom:0}.slash-command-item:hover{background:var(--ate-surface-secondary)}.slash-command-item.selected{background:var(--ate-primary-light);border-color:var(--ate-primary-light-alpha)}.slash-command-icon{display:flex;align-items:center;justify-content:center;width:32px;height:32px;background:var(--ate-surface-tertiary);border-radius:var(--ate-sub-border-radius, 8px);color:var(--ate-primary);flex-shrink:0;transition:all .15s ease}.slash-command-item.selected .slash-command-icon{background:var(--ate-primary);color:var(--ate-primary-contrast, #ffffff)}.slash-command-icon .material-symbols-outlined{font-size:18px}.slash-command-content{flex:1;min-width:0}.slash-command-title{font-weight:500;color:var(--ate-text);font-size:14px;margin-bottom:1px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.slash-command-description{color:var(--ate-text-secondary);font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n"] }]
5230
- }], ctorParameters: () => [], propDecorators: { editor: [{ type: i0.Input, args: [{ isSignal: true, alias: "editor", required: true }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], menuRef: [{
5231
- type: ViewChild,
5232
- args: ["menuRef", { static: false }]
5233
- }] } });
5317
+ }], ctorParameters: () => [], propDecorators: { editor: [{ type: i0.Input, args: [{ isSignal: true, alias: "editor", required: true }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], menuRef: [{ type: i0.ViewChild, args: ["menuRef", { isSignal: true }] }] } });
5234
5318
 
5235
5319
  /**
5236
5320
  * Edit Toggle Component
@@ -5311,12 +5395,15 @@ const AteSelectionCalculator = editor => {
5311
5395
  const { selection } = editor.state;
5312
5396
  const { from, to } = selection;
5313
5397
  let selectionType = "none";
5314
- if (selection instanceof TextSelection)
5398
+ if (selection instanceof TextSelection) {
5315
5399
  selectionType = "text";
5316
- else if (selection instanceof NodeSelection)
5400
+ }
5401
+ else if (selection instanceof NodeSelection) {
5317
5402
  selectionType = "node";
5318
- else if (selection instanceof CellSelection)
5403
+ }
5404
+ else if (selection instanceof CellSelection) {
5319
5405
  selectionType = "cell";
5406
+ }
5320
5407
  let isSingleCell = false;
5321
5408
  if (selection instanceof CellSelection) {
5322
5409
  isSingleCell = selection.$anchorCell.pos === selection.$headCell.pos;
@@ -5348,8 +5435,9 @@ const AteMarksCalculator = editor => {
5348
5435
  const isInsideInlineCode = isCode;
5349
5436
  // 1. Resolve target element once for this calculation
5350
5437
  const getTargetElement = () => {
5351
- if (typeof window === "undefined" || !editor.view?.dom)
5438
+ if (typeof window === "undefined" || !editor.view?.dom) {
5352
5439
  return null;
5440
+ }
5353
5441
  try {
5354
5442
  const { from } = editor.state.selection;
5355
5443
  const { node } = editor.view.domAtPos(from);
@@ -5363,8 +5451,9 @@ const AteMarksCalculator = editor => {
5363
5451
  const computedStyle = targetEl && typeof window !== "undefined" ? window.getComputedStyle(targetEl) : null;
5364
5452
  // 2. Lightweight helper to extract properties from the pre-calculated style object
5365
5453
  const getStyle = (prop) => {
5366
- if (!computedStyle)
5454
+ if (!computedStyle) {
5367
5455
  return null;
5456
+ }
5368
5457
  const val = computedStyle.getPropertyValue(prop);
5369
5458
  return normalizeColor(val);
5370
5459
  };
@@ -5386,7 +5475,8 @@ const AteMarksCalculator = editor => {
5386
5475
  computedColor: colorMark || getStyle("color"),
5387
5476
  background: backgroundMark,
5388
5477
  computedBackground: backgroundMark || getStyle("background-color"),
5389
- linkOpenOnClick: editor.extensionManager.extensions.find(ext => ext.name === "link")?.options?.openOnClick ?? false,
5478
+ linkOpenOnClick: editor.extensionManager.extensions.find(ext => ext.name === "link")?.options?.openOnClick ??
5479
+ false,
5390
5480
  },
5391
5481
  can: {
5392
5482
  toggleBold: marksAllowed && !isInsideInlineCode && editor.can().toggleBold(),
@@ -5395,7 +5485,9 @@ const AteMarksCalculator = editor => {
5395
5485
  toggleStrike: marksAllowed && !isInsideInlineCode && editor.can().toggleStrike(),
5396
5486
  toggleCode: marksAllowed && editor.can().toggleCode(),
5397
5487
  toggleHighlight: marksAllowed && !isInsideInlineCode && editor.can().toggleHighlight(),
5398
- toggleLink: marksAllowed && !isInsideInlineCode && (editor.can().setLink({ href: "" }) || editor.can().unsetLink()),
5488
+ toggleLink: marksAllowed &&
5489
+ !isInsideInlineCode &&
5490
+ (editor.can().setLink({ href: "" }) || editor.can().unsetLink()),
5399
5491
  toggleSuperscript: marksAllowed && !isInsideInlineCode && editor.can().toggleSuperscript(),
5400
5492
  toggleSubscript: marksAllowed && !isInsideInlineCode && editor.can().toggleSubscript(),
5401
5493
  setColor: marksAllowed && !isInsideInlineCode && editor.can().setColor(""),
@@ -5522,7 +5614,16 @@ const AteDiscoveryCalculator = (editor) => {
5522
5614
  const name = extension.name;
5523
5615
  const type = extension.type;
5524
5616
  // Skip internal/core extensions or already handled ones
5525
- if (["selection", "editable", "focus", "undo", "redo", "history", "placeholder", "characterCount"].includes(name)) {
5617
+ if ([
5618
+ "selection",
5619
+ "editable",
5620
+ "focus",
5621
+ "undo",
5622
+ "redo",
5623
+ "history",
5624
+ "placeholder",
5625
+ "characterCount",
5626
+ ].includes(name)) {
5526
5627
  return;
5527
5628
  }
5528
5629
  if (handled.includes(name)) {
@@ -5545,13 +5646,16 @@ const AteLinkClickBehavior = Extension.create({
5545
5646
  new Plugin({
5546
5647
  key: new PluginKey("linkClickBehavior"),
5547
5648
  props: {
5548
- handleClick(view, _pos, _event) {
5649
+ handleClick(view, _pos, event) {
5549
5650
  // handleClick only runs in the browser, but we guard it for absolute SSR safety
5550
- if (typeof window === "undefined")
5651
+ if (typeof window === "undefined") {
5551
5652
  return false;
5552
- // If editor is editable, let TipTap/BubbleMenu handle it
5553
- if (view.editable)
5653
+ }
5654
+ const isModKey = event.ctrlKey || event.metaKey;
5655
+ // If editor is editable, only proceed if Ctrl/Cmd is pressed
5656
+ if (view.editable && !isModKey) {
5554
5657
  return false;
5658
+ }
5555
5659
  const attrs = getAttributes(view.state, "link");
5556
5660
  if (attrs["href"]) {
5557
5661
  window.open(attrs["href"], "_blank", "noopener,noreferrer");
@@ -5753,12 +5857,14 @@ class AngularTiptapEditorComponent {
5753
5857
  // Appearance & Fundamentals
5754
5858
  this.finalSeamless = computed(() => {
5755
5859
  const fromConfig = this.config().mode;
5756
- if (fromConfig !== undefined)
5860
+ if (fromConfig !== undefined) {
5757
5861
  return fromConfig === "seamless";
5862
+ }
5758
5863
  return this.seamless();
5759
5864
  }, ...(ngDevMode ? [{ debugName: "finalSeamless" }] : []));
5760
5865
  this.finalEditable = computed(() => this.config().editable ?? this.editable(), ...(ngDevMode ? [{ debugName: "finalEditable" }] : []));
5761
- this.finalPlaceholder = computed(() => this.config().placeholder ?? (this.placeholder() || this.currentTranslations().editor.placeholder), ...(ngDevMode ? [{ debugName: "finalPlaceholder" }] : []));
5866
+ this.finalPlaceholder = computed(() => this.config().placeholder ??
5867
+ (this.placeholder() || this.currentTranslations().editor.placeholder), ...(ngDevMode ? [{ debugName: "finalPlaceholder" }] : []));
5762
5868
  this.finalFillContainer = computed(() => this.config().fillContainer ?? this.fillContainer(), ...(ngDevMode ? [{ debugName: "finalFillContainer" }] : []));
5763
5869
  this.finalShowFooter = computed(() => this.config().showFooter ?? this.showFooter(), ...(ngDevMode ? [{ debugName: "finalShowFooter" }] : []));
5764
5870
  this.finalShowEditToggle = computed(() => this.config().showEditToggle ?? this.showEditToggle(), ...(ngDevMode ? [{ debugName: "finalShowEditToggle" }] : []));
@@ -5772,8 +5878,9 @@ class AngularTiptapEditorComponent {
5772
5878
  this.finalToolbarConfig = computed(() => {
5773
5879
  const fromConfig = this.config().toolbar;
5774
5880
  const base = ATE_DEFAULT_TOOLBAR_CONFIG;
5775
- if (fromConfig)
5881
+ if (fromConfig) {
5776
5882
  return { ...base, ...fromConfig };
5883
+ }
5777
5884
  const fromInput = this.toolbar();
5778
5885
  return Object.keys(fromInput).length === 0 ? base : { ...base, ...fromInput };
5779
5886
  }, ...(ngDevMode ? [{ debugName: "finalToolbarConfig" }] : []));
@@ -5782,40 +5889,51 @@ class AngularTiptapEditorComponent {
5782
5889
  this.finalBubbleMenuConfig = computed(() => {
5783
5890
  const fromConfig = this.config().bubbleMenu;
5784
5891
  const base = ATE_DEFAULT_BUBBLE_MENU_CONFIG;
5785
- if (fromConfig)
5892
+ if (fromConfig) {
5786
5893
  return { ...base, ...fromConfig };
5894
+ }
5787
5895
  return Object.keys(this.bubbleMenu()).length === 0 ? base : { ...base, ...this.bubbleMenu() };
5788
5896
  }, ...(ngDevMode ? [{ debugName: "finalBubbleMenuConfig" }] : []));
5789
5897
  this.finalShowImageBubbleMenu = computed(() => this.config().showImageBubbleMenu ?? this.showImageBubbleMenu(), ...(ngDevMode ? [{ debugName: "finalShowImageBubbleMenu" }] : []));
5790
5898
  this.finalImageBubbleMenuConfig = computed(() => {
5791
5899
  const fromConfig = this.config().imageBubbleMenu;
5792
5900
  const base = ATE_DEFAULT_IMAGE_BUBBLE_MENU_CONFIG;
5793
- if (fromConfig)
5901
+ if (fromConfig) {
5794
5902
  return { ...base, ...fromConfig };
5795
- return Object.keys(this.imageBubbleMenu()).length === 0 ? base : { ...base, ...this.imageBubbleMenu() };
5903
+ }
5904
+ return Object.keys(this.imageBubbleMenu()).length === 0
5905
+ ? base
5906
+ : { ...base, ...this.imageBubbleMenu() };
5796
5907
  }, ...(ngDevMode ? [{ debugName: "finalImageBubbleMenuConfig" }] : []));
5797
5908
  this.finalShowTableBubbleMenu = computed(() => this.config().showTableMenu ?? this.showTableBubbleMenu(), ...(ngDevMode ? [{ debugName: "finalShowTableBubbleMenu" }] : []));
5798
5909
  this.finalTableBubbleMenuConfig = computed(() => {
5799
5910
  const fromConfig = this.config().tableBubbleMenu;
5800
5911
  const base = ATE_DEFAULT_TABLE_MENU_CONFIG;
5801
- if (fromConfig)
5912
+ if (fromConfig) {
5802
5913
  return { ...base, ...fromConfig };
5803
- return Object.keys(this.tableBubbleMenu()).length === 0 ? base : { ...base, ...this.tableBubbleMenu() };
5914
+ }
5915
+ return Object.keys(this.tableBubbleMenu()).length === 0
5916
+ ? base
5917
+ : { ...base, ...this.tableBubbleMenu() };
5804
5918
  }, ...(ngDevMode ? [{ debugName: "finalTableBubbleMenuConfig" }] : []));
5805
5919
  this.finalShowCellBubbleMenu = computed(() => this.config().showCellMenu ?? this.showCellBubbleMenu(), ...(ngDevMode ? [{ debugName: "finalShowCellBubbleMenu" }] : []));
5806
5920
  this.finalCellBubbleMenuConfig = computed(() => {
5807
5921
  const fromConfig = this.config().cellBubbleMenu;
5808
5922
  const base = ATE_DEFAULT_CELL_MENU_CONFIG;
5809
- if (fromConfig)
5923
+ if (fromConfig) {
5810
5924
  return { ...base, ...fromConfig };
5811
- return Object.keys(this.cellBubbleMenu()).length === 0 ? base : { ...base, ...this.cellBubbleMenu() };
5925
+ }
5926
+ return Object.keys(this.cellBubbleMenu()).length === 0
5927
+ ? base
5928
+ : { ...base, ...this.cellBubbleMenu() };
5812
5929
  }, ...(ngDevMode ? [{ debugName: "finalCellBubbleMenuConfig" }] : []));
5813
5930
  this.finalEnableSlashCommands = computed(() => this.config().enableSlashCommands ?? this.enableSlashCommands(), ...(ngDevMode ? [{ debugName: "finalEnableSlashCommands" }] : []));
5814
5931
  this.finalSlashCommandsConfig = computed(() => {
5815
5932
  const fromConfig = this.config().slashCommands;
5816
5933
  const customConfig = this.customSlashCommands();
5817
- if (customConfig)
5934
+ if (customConfig) {
5818
5935
  return customConfig;
5936
+ }
5819
5937
  let baseConfig = this.slashCommands();
5820
5938
  if (fromConfig) {
5821
5939
  baseConfig = fromConfig;
@@ -5875,17 +5993,21 @@ class AngularTiptapEditorComponent {
5875
5993
  untracked(() => {
5876
5994
  const editor = this.editor();
5877
5995
  const hasFormControl = !!this.ngControl?.control;
5878
- if (!editor || content === undefined)
5996
+ if (!editor || content === undefined) {
5879
5997
  return;
5998
+ }
5880
5999
  // Anti-écho : on ignore ce qu'on vient d'émettre nous-mêmes
5881
- if (content === this.lastEmittedHtml)
6000
+ if (content === this.lastEmittedHtml) {
5882
6001
  return;
6002
+ }
5883
6003
  // Double sécurité : on vérifie le contenu actuel de l'éditeur
5884
- if (content === editor.getHTML())
6004
+ if (content === editor.getHTML()) {
5885
6005
  return;
6006
+ }
5886
6007
  // Do not overwrite content if we have a FormControl and content is empty
5887
- if (hasFormControl && !content)
6008
+ if (hasFormControl && !content) {
5888
6009
  return;
6010
+ }
5889
6011
  editor.commands.setContent(content, false);
5890
6012
  });
5891
6013
  });
@@ -6017,7 +6139,9 @@ class AngularTiptapEditorComponent {
6017
6139
  // Allow addition of custom extensions, but avoid duplicates by filtering by name
6018
6140
  const customExtensions = this.tiptapExtensions();
6019
6141
  if (customExtensions.length > 0) {
6020
- const existingNames = new Set(extensions.map(ext => ext?.name).filter((name) => !!name));
6142
+ const existingNames = new Set(extensions
6143
+ .map(ext => ext?.name)
6144
+ .filter((name) => !!name));
6021
6145
  const toAdd = customExtensions.filter(ext => {
6022
6146
  const name = ext?.name;
6023
6147
  return !name || !existingNames.has(name);
@@ -6082,7 +6206,8 @@ class AngularTiptapEditorComponent {
6082
6206
  this.editableChange.emit(newEditable);
6083
6207
  }
6084
6208
  updateCharacterCount(editor) {
6085
- if ((this.finalShowCharacterCount() || this.finalShowWordCount()) && editor.storage["characterCount"]) {
6209
+ if ((this.finalShowCharacterCount() || this.finalShowWordCount()) &&
6210
+ editor.storage["characterCount"]) {
6086
6211
  const storage = editor.storage["characterCount"];
6087
6212
  this._characterCount.set(storage.characters());
6088
6213
  this._wordCount.set(storage.words());
@@ -6183,8 +6308,9 @@ class AngularTiptapEditorComponent {
6183
6308
  }
6184
6309
  onEditorClick(event) {
6185
6310
  const editor = this.editor();
6186
- if (!editor || !this.finalEditable())
6311
+ if (!editor || !this.finalEditable()) {
6187
6312
  return;
6313
+ }
6188
6314
  // Verify if interaction is on the container element and not on the content
6189
6315
  const target = event.target;
6190
6316
  const editorElement = this.editorElement()?.nativeElement;
@@ -6290,7 +6416,10 @@ class AngularTiptapEditorComponent {
6290
6416
 
6291
6417
  <!-- Counters -->
6292
6418
  @if (
6293
- finalEditable() && !mergedDisabled() && finalShowFooter() && (finalShowCharacterCount() || finalShowWordCount())
6419
+ finalEditable() &&
6420
+ !mergedDisabled() &&
6421
+ finalShowFooter() &&
6422
+ (finalShowCharacterCount() || finalShowWordCount())
6294
6423
  ) {
6295
6424
  <div
6296
6425
  class="character-count"
@@ -6314,7 +6443,7 @@ class AngularTiptapEditorComponent {
6314
6443
  </div>
6315
6444
  }
6316
6445
  </div>
6317
- `, isInline: true, styles: [":host{--ate-primary: #2563eb;--ate-primary-contrast: #ffffff;--ate-primary-light: color-mix(in srgb, var(--ate-primary), transparent 90%);--ate-primary-lighter: color-mix(in srgb, var(--ate-primary), transparent 95%);--ate-primary-light-alpha: color-mix(in srgb, var(--ate-primary), transparent 85%);--ate-surface: #ffffff;--ate-surface-secondary: #f8f9fa;--ate-surface-tertiary: #f1f5f9;--ate-text: #2d3748;--ate-text-secondary: #64748b;--ate-text-muted: #a0aec0;--ate-border: #e2e8f0;--ate-highlight-bg: #fef08a;--ate-highlight-color: #854d0e;--ate-button-hover: #f1f5f9;--ate-button-active: #e2e8f0;--ate-error-color: #c53030;--ate-error-bg: #fed7d7;--ate-error-border: #feb2b2;--ate-border-color: var(--ate-border);--ate-border-width: 2px;--ate-border-radius: 12px;--ate-focus-color: var(--ate-primary);--ate-background: var(--ate-surface);--ate-sub-border-radius: 8px;--ate-text-color: var(--ate-text);--ate-placeholder-color: var(--ate-text-muted);--ate-line-height: 1.6;--ate-content-padding: 16px;--ate-menu-bg: var(--ate-surface);--ate-menu-border-radius: var(--ate-border-radius);--ate-menu-border: var(--ate-border);--ate-menu-shadow: 0 10px 15px -3px rgba(0, 0, 0, .1), 0 4px 6px -2px rgba(0, 0, 0, .05);--ate-menu-padding: 6px;--ate-toolbar-padding: var(--ate-menu-padding);--ate-toolbar-background: var(--ate-surface-secondary);--ate-toolbar-border-color: var(--ate-border);--ate-toolbar-button-color: var(--ate-text-secondary);--ate-toolbar-button-hover-background: transparent;--ate-toolbar-button-active-background: var(--ate-primary-light);--ate-toolbar-button-active-color: var(--ate-primary);--ate-counter-color: var(--ate-text-secondary);--ate-counter-background: var(--ate-surface-secondary);--ate-counter-border-color: var(--ate-border);--ate-drag-background: #f0f8ff;--ate-drag-border-color: var(--ate-primary);--ate-blockquote-border-color: var(--ate-border);--ate-blockquote-background: var(--ate-surface-secondary);--ate-code-background: var(--ate-surface-secondary);--ate-code-color: var(--ate-code-color);--ate-code-border-color: var(--ate-border);--ate-code-block-background: #0f172a;--ate-code-block-color: #e2e8f0;--ate-code-block-border-color: var(--ate-border);--ate-image-border-radius: 16px;--ate-image-selected-color: var(--ate-primary);--ate-scrollbar-width: 10px;--ate-scrollbar-thumb: var(--ate-border);--ate-scrollbar-thumb-hover: var(--ate-text-muted);--ate-scrollbar-track: transparent;--ate-table-border-color: var(--ate-border);--ate-table-header-background: var(--ate-surface-secondary);--ate-table-header-color: var(--ate-text);--ate-table-cell-background: var(--ate-surface);--ate-table-cell-selected-background: var(--ate-primary-light);--ate-table-resize-handle-color: var(--ate-primary);--ate-table-row-hover-background: var(--ate-primary-lighter)}:host(.dark),:host([data-theme=\"dark\"]){--ate-primary: #3b82f6;--ate-primary-contrast: #ffffff;--ate-primary-light: color-mix(in srgb, var(--ate-primary), transparent 85%);--ate-primary-lighter: color-mix(in srgb, var(--ate-primary), transparent 92%);--ate-primary-light-alpha: color-mix(in srgb, var(--ate-primary), transparent 80%);--ate-surface: #020617;--ate-surface-secondary: #0f172a;--ate-surface-tertiary: #1e293b;--ate-text: #f8fafc;--ate-text-secondary: #94a3b8;--ate-text-muted: #64748b;--ate-border: #1e293b;--ate-highlight-bg: #854d0e;--ate-highlight-color: #fef08a;--ate-button-hover: #1e293b;--ate-button-active: #0f172a;--ate-menu-border: rgba(255, 255, 255, .1);--ate-menu-shadow: 0 20px 25px -5px rgba(0, 0, 0, .3), 0 10px 10px -5px rgba(0, 0, 0, .2);--ate-error-color: #f87171;--ate-error-bg: #450a0a;--ate-error-border: #7f1d1d;--ate-drag-background: var(--ate-surface-tertiary);--ate-drag-border-color: var(--ate-primary);--ate-blockquote-border-color: var(--ate-primary);--ate-toolbar-button-active-background: var(--ate-primary-light);--ate-toolbar-button-active-color: var(--ate-primary);--ate-button-hover: var(--ate-surface-tertiary);--ate-button-active: var(--ate-surface-secondary);--ate-scrollbar-thumb: var(--ate-surface-tertiary);--ate-scrollbar-thumb-hover: var(--ate-text-muted)}:host(.fill-container){display:block;height:100%}.ate-editor{border:var(--ate-border-width) solid var(--ate-border-color);border-radius:var(--ate-border-radius);background:var(--ate-background);overflow:visible;transition:border-color .2s ease;position:relative}:host(.floating-toolbar) .ate-editor{overflow:visible}:host(.fill-container) .ate-editor{display:flex;flex-direction:column;height:100%}:host(.fill-container) .ate-content-wrapper{flex:1;min-height:0}:host(.fill-container) .ate-content{flex:1;min-height:0;overflow-y:auto}.ate-editor:focus-within{border-color:var(--ate-focus-color)}.ate-content{min-height:var(--editor-min-height, 200px);height:var(--editor-height, auto);max-height:var(--editor-max-height, none);overflow-y:var(--editor-overflow, visible);outline:none;position:relative;scrollbar-width:thin;scrollbar-color:var(--ate-scrollbar-thumb) var(--ate-scrollbar-track)}:host(.is-disabled) .ate-content{cursor:not-allowed;opacity:.7;-webkit-user-select:none;user-select:none;pointer-events:none;background-color:var(--ate-surface-tertiary)}:host(.is-readonly) .ate-content{cursor:default;-webkit-user-select:text;user-select:text}:host(.is-readonly) .ate-content ::ng-deep .ate-link{cursor:pointer;pointer-events:auto}.ate-content::-webkit-scrollbar{width:var(--ate-scrollbar-width)}.ate-content-wrapper{position:relative;display:flex;flex-direction:column;min-height:0}.ate-content-wrapper .ate-content{flex:1}.ate-content::-webkit-scrollbar-track{background:var(--ate-scrollbar-track)}.ate-content::-webkit-scrollbar-thumb{background:var(--ate-scrollbar-thumb);border:3px solid transparent;background-clip:content-box;border-radius:10px}.ate-content::-webkit-scrollbar-thumb:hover{background:var(--ate-scrollbar-thumb-hover);background-clip:content-box}.ate-content.drag-over{background:var(--ate-drag-background);border:2px dashed var(--ate-drag-border-color)}.character-count{padding:6px 8px;font-size:12px;color:var(--ate-counter-color);text-align:right;border-top:1px solid var(--ate-counter-border-color);background:var(--ate-counter-background);transition:color .2s ease;border-bottom-left-radius:calc(var(--ate-border-radius) - var(--ate-border-width));border-bottom-right-radius:calc(var(--ate-border-radius) - var(--ate-border-width))}.character-count.limit-reached{color:var(--ate-error-color, #ef4444);font-weight:600}:host ::ng-deep .ProseMirror{padding:var(--ate-content-padding);outline:none;line-height:var(--ate-line-height);color:var(--ate-text-color);min-height:100%;height:100%;word-wrap:break-word;overflow-wrap:break-word}:host ::ng-deep .ProseMirror h1{font-size:2em;font-weight:700;margin-top:0;margin-bottom:.5em}:host ::ng-deep .ProseMirror h2{font-size:1.5em;font-weight:700;margin-top:1em;margin-bottom:.5em}:host ::ng-deep .ProseMirror h3{font-size:1.25em;font-weight:700;margin-top:1em;margin-bottom:.5em}:host ::ng-deep .ProseMirror p{margin:.5em 0}:host ::ng-deep .ProseMirror ul,:host ::ng-deep .ProseMirror ol{padding-left:2em;margin:.5em 0}:host ::ng-deep .ProseMirror blockquote{border-left:4px solid var(--ate-blockquote-border-color);margin:1em 0;background:var(--ate-blockquote-background);padding:.5em 1em;border-radius:0 4px 4px 0}:host ::ng-deep .ProseMirror code{background:var(--ate-code-background);color:var(--ate-code-color);border:1px solid var(--ate-code-border-color);padding:.15em .4em;border-radius:4px;font-family:JetBrains Mono,Fira Code,Monaco,Consolas,monospace;font-size:.85em;font-weight:500}:host ::ng-deep .ProseMirror pre{background:var(--ate-code-block-background);color:var(--ate-code-block-color);border:1px solid var(--ate-code-block-border-color);padding:1em;border-radius:var(--ate-border-radius);overflow-x:auto;margin:1em 0}:host ::ng-deep .ProseMirror pre code{background:none;color:inherit;border:none;padding:0}:host ::ng-deep .ProseMirror p.is-editor-empty:first-child:before{content:attr(data-placeholder);color:var(--ate-placeholder-color);pointer-events:none;float:left;height:0}:host ::ng-deep .ProseMirror[contenteditable=false]{pointer-events:none}:host ::ng-deep .ProseMirror[contenteditable=false] img{cursor:default;pointer-events:none}:host ::ng-deep .ProseMirror[contenteditable=false] img:hover{transform:none;box-shadow:0 2px 8px #0000001a}:host ::ng-deep .ProseMirror[contenteditable=false] img.ProseMirror-selectednode{outline:none}:host ::ng-deep .ProseMirror img{position:relative;display:inline-block;max-width:100%;height:auto;cursor:pointer;transition:all .2s ease;border:2px solid transparent;border-radius:var(--ate-image-border-radius)}:host ::ng-deep .ProseMirror img:hover{border-color:var(--ate-border-color);box-shadow:0 2px 4px #0000001a}:host ::ng-deep .ProseMirror img.ProseMirror-selectednode{border-color:var(--ate-image-selected-color);box-shadow:0 0 0 3px var(--ate-primary-light-alpha);transition:all .2s ease}:host ::ng-deep .ProseMirror .tiptap-image{max-width:100%;height:auto;border-radius:var(--ate-image-border-radius);box-shadow:0 4px 20px #00000014;margin:.5em 0;cursor:pointer;transition:all .3s cubic-bezier(.4,0,.2,1);display:block;filter:brightness(1) contrast(1)}:host ::ng-deep .ProseMirror .tiptap-image:hover{box-shadow:0 8px 30px #0000001f;filter:brightness(1.02) contrast(1.02)}:host ::ng-deep .ProseMirror .tiptap-image.ProseMirror-selectednode{outline:2px solid var(--ate-primary);outline-offset:2px;border-radius:var(--ate-image-border-radius);box-shadow:0 0 0 4px var(--ate-primary-light-alpha)}:host ::ng-deep .image-container{margin:.5em 0;text-align:center;border-radius:16px;overflow:hidden;transition:all .3s cubic-bezier(.4,0,.2,1)}:host ::ng-deep .image-container.image-align-left{text-align:left}:host ::ng-deep .image-container.image-align-center{text-align:center}:host ::ng-deep .image-container.image-align-right{text-align:right}:host ::ng-deep .image-container img{display:inline-block;max-width:100%;height:auto;border-radius:16px}:host ::ng-deep .resizable-image-container{position:relative;display:inline-block;margin:.5em 0}:host ::ng-deep .resize-controls{position:absolute;inset:0;pointer-events:none;z-index:1000}:host ::ng-deep .resize-handle{position:absolute;width:12px;height:12px;background:var(--ate-primary);border:2px solid var(--ate-surface);border-radius:50%;pointer-events:all;cursor:pointer;z-index:1001;transition:all .15s ease;box-shadow:0 2px 6px #0003}:host ::ng-deep .resize-handle:hover{background:var(--ate-primary);box-shadow:0 3px 8px #0000004d}:host ::ng-deep .resize-handle:active{background:var(--ate-primary)}:host ::ng-deep .resize-handle-n:hover,:host ::ng-deep .resize-handle-s:hover{transform:translate(-50%) scale(1.2)}:host ::ng-deep .resize-handle-w:hover,:host ::ng-deep .resize-handle-e:hover{transform:translateY(-50%) scale(1.2)}:host ::ng-deep .resize-handle-n:active,:host ::ng-deep .resize-handle-s:active{transform:translate(-50%) scale(.9)}:host ::ng-deep .resize-handle-w:active,:host ::ng-deep .resize-handle-e:active{transform:translateY(-50%) scale(.9)}:host ::ng-deep .resize-handle-nw:hover,:host ::ng-deep .resize-handle-ne:hover,:host ::ng-deep .resize-handle-sw:hover,:host ::ng-deep .resize-handle-se:hover{transform:scale(1.2)}:host ::ng-deep .resize-handle-nw:active,:host ::ng-deep .resize-handle-ne:active,:host ::ng-deep .resize-handle-sw:active,:host ::ng-deep .resize-handle-se:active{transform:scale(.9)}:host ::ng-deep .resize-handle-nw{top:0;left:-6px;cursor:nw-resize}:host ::ng-deep .resize-handle-n{top:0;left:50%;transform:translate(-50%);cursor:n-resize}:host ::ng-deep .resize-handle-ne{top:0;right:-6px;cursor:ne-resize}:host ::ng-deep .resize-handle-w{top:50%;left:-6px;transform:translateY(-50%);cursor:w-resize}:host ::ng-deep .resize-handle-e{top:50%;right:-6px;transform:translateY(-50%);cursor:e-resize}:host ::ng-deep .resize-handle-sw{bottom:0;left:-6px;cursor:sw-resize}:host ::ng-deep .resize-handle-s{bottom:0;left:50%;transform:translate(-50%);cursor:s-resize}:host ::ng-deep .resize-handle-se{bottom:0;right:-6px;cursor:se-resize}:host ::ng-deep body.resizing{-webkit-user-select:none;user-select:none;cursor:crosshair}:host ::ng-deep body.resizing .ProseMirror{pointer-events:none}:host ::ng-deep body.resizing .ProseMirror .tiptap-image{pointer-events:none}:host ::ng-deep .image-size-info{position:absolute;bottom:-20px;left:50%;transform:translate(-50%);background:#000c;color:#fff;padding:2px 6px;border-radius:3px;font-size:11px;white-space:nowrap;opacity:0;transition:opacity .2s ease}:host ::ng-deep .image-container:hover .image-size-info{opacity:1}:host ::ng-deep .ProseMirror table{border-collapse:separate;border-spacing:0;margin:0;table-layout:fixed;width:100%;border-radius:8px;overflow:hidden}:host ::ng-deep .ProseMirror table td,:host ::ng-deep .ProseMirror table th{border:none;border-right:1px solid var(--ate-table-border-color);border-bottom:1px solid var(--ate-table-border-color);box-sizing:border-box;min-width:1em;padding:8px 12px;position:relative;vertical-align:top;text-align:left}:host ::ng-deep .ProseMirror table td{background:var(--ate-table-cell-background)}:host ::ng-deep .ProseMirror table td:first-child,:host ::ng-deep .ProseMirror table th:first-child{border-left:1px solid var(--ate-table-border-color)}:host ::ng-deep .ProseMirror table tr:first-child td,:host ::ng-deep .ProseMirror table tr:first-child th{border-top:1px solid var(--ate-table-border-color)}:host ::ng-deep .ProseMirror table tr:first-child th:first-child{border-top-left-radius:8px}:host ::ng-deep .ProseMirror table tr:first-child th:last-child{border-top-right-radius:8px}:host ::ng-deep .ProseMirror table tr:last-child td:first-child{border-bottom-left-radius:8px}:host ::ng-deep .ProseMirror table tr:last-child td:last-child{border-bottom-right-radius:8px}:host ::ng-deep .ProseMirror table th{background:var(--ate-table-header-background);font-weight:600;color:var(--ate-table-header-color)}:host ::ng-deep .ProseMirror table .selectedCell:after{background:var(--ate-table-cell-selected-background);content:\"\";inset:0;pointer-events:none;position:absolute;z-index:2}:host ::ng-deep .ProseMirror table .column-resize-handle{position:absolute;right:-2px;top:0;bottom:0;width:4px;background-color:var(--ate-table-resize-handle-color);opacity:0;transition:opacity .2s ease}:host ::ng-deep .ProseMirror table:hover .column-resize-handle{opacity:1}:host ::ng-deep .ProseMirror table .column-resize-handle:hover{background-color:var(--ate-focus-color)}:host ::ng-deep .ProseMirror .tableWrapper{overflow-x:auto;margin:1em 0;border-radius:8px}:host ::ng-deep .ProseMirror .tableWrapper table{margin:0;border-radius:8px;min-width:600px;overflow:hidden}:host ::ng-deep .ProseMirror table p{margin:0}:host ::ng-deep .ProseMirror table tbody tr:hover td{background-color:var(--ate-table-row-hover-background)}\n"], dependencies: [{ kind: "component", type: AteToolbarComponent, selector: "ate-toolbar", inputs: ["editor", "config", "imageUpload", "floating"] }, { kind: "component", type: AteBubbleMenuComponent, selector: "ate-bubble-menu", inputs: ["config"] }, { kind: "component", type: AteImageBubbleMenuComponent, selector: "ate-image-bubble-menu", inputs: ["config", "imageUpload"] }, { kind: "component", type: AteTableBubbleMenuComponent, selector: "ate-table-bubble-menu", inputs: ["config"] }, { kind: "component", type: AteCellBubbleMenuComponent, selector: "ate-cell-bubble-menu", inputs: ["config"] }, { kind: "component", type: AteSlashCommandsComponent, selector: "ate-slash-commands", inputs: ["editor", "config"] }, { kind: "component", type: AteLinkBubbleMenuComponent, selector: "ate-link-bubble-menu", inputs: ["editor"] }, { kind: "component", type: AteColorBubbleMenuComponent, selector: "ate-color-bubble-menu", inputs: ["editor"] }, { kind: "component", type: AteEditToggleComponent, selector: "ate-edit-toggle", inputs: ["editable", "translations"], outputs: ["editToggle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
6446
+ `, isInline: true, styles: [":host{--ate-primary: #2563eb;--ate-primary-contrast: #ffffff;--ate-primary-light: color-mix(in srgb, var(--ate-primary), transparent 90%);--ate-primary-lighter: color-mix(in srgb, var(--ate-primary), transparent 95%);--ate-primary-light-alpha: color-mix(in srgb, var(--ate-primary), transparent 85%);--ate-surface: #ffffff;--ate-surface-secondary: #f8f9fa;--ate-surface-tertiary: #f1f5f9;--ate-text: #2d3748;--ate-text-secondary: #64748b;--ate-text-muted: #a0aec0;--ate-border: #e2e8f0;--ate-highlight-bg: #fef08a;--ate-highlight-color: #854d0e;--ate-button-hover: #f1f5f9;--ate-button-active: #e2e8f0;--ate-error-color: #c53030;--ate-error-bg: #fed7d7;--ate-error-border: #feb2b2;--ate-border-color: var(--ate-border);--ate-border-width: 2px;--ate-border-radius: 12px;--ate-focus-color: var(--ate-primary);--ate-background: var(--ate-surface);--ate-sub-border-radius: 8px;--ate-text-color: var(--ate-text);--ate-placeholder-color: var(--ate-text-muted);--ate-line-height: 1.6;--ate-content-padding: 16px;--ate-menu-bg: var(--ate-surface);--ate-menu-border-radius: var(--ate-border-radius);--ate-menu-border: var(--ate-border);--ate-menu-shadow: 0 10px 15px -3px rgba(0, 0, 0, .1), 0 4px 6px -2px rgba(0, 0, 0, .05);--ate-menu-padding: 6px;--ate-toolbar-padding: var(--ate-menu-padding);--ate-toolbar-background: var(--ate-surface-secondary);--ate-toolbar-border-color: var(--ate-border);--ate-toolbar-button-color: var(--ate-text-secondary);--ate-toolbar-button-hover-background: transparent;--ate-toolbar-button-active-background: var(--ate-primary-light);--ate-toolbar-button-active-color: var(--ate-primary);--ate-counter-color: var(--ate-text-secondary);--ate-counter-background: var(--ate-surface-secondary);--ate-counter-border-color: var(--ate-border);--ate-drag-background: #f0f8ff;--ate-drag-border-color: var(--ate-primary);--ate-blockquote-border-color: var(--ate-border);--ate-blockquote-background: var(--ate-surface-secondary);--ate-code-background: var(--ate-surface-secondary);--ate-code-color: var(--ate-code-color);--ate-code-border-color: var(--ate-border);--ate-code-block-background: #0f172a;--ate-code-block-color: #e2e8f0;--ate-code-block-border-color: var(--ate-border);--ate-image-border-radius: 16px;--ate-image-selected-color: var(--ate-primary);--ate-scrollbar-width: 10px;--ate-scrollbar-thumb: var(--ate-border);--ate-scrollbar-thumb-hover: var(--ate-text-muted);--ate-scrollbar-track: transparent;--ate-table-border-color: var(--ate-border);--ate-table-header-background: var(--ate-surface-secondary);--ate-table-header-color: var(--ate-text);--ate-table-cell-background: var(--ate-surface);--ate-table-cell-selected-background: var(--ate-primary-light);--ate-table-resize-handle-color: var(--ate-primary);--ate-table-row-hover-background: var(--ate-primary-lighter)}:host(.dark),:host([data-theme=\"dark\"]){--ate-primary: #3b82f6;--ate-primary-contrast: #ffffff;--ate-primary-light: color-mix(in srgb, var(--ate-primary), transparent 85%);--ate-primary-lighter: color-mix(in srgb, var(--ate-primary), transparent 92%);--ate-primary-light-alpha: color-mix(in srgb, var(--ate-primary), transparent 80%);--ate-surface: #020617;--ate-surface-secondary: #0f172a;--ate-surface-tertiary: #1e293b;--ate-text: #f8fafc;--ate-text-secondary: #94a3b8;--ate-text-muted: #64748b;--ate-border: #1e293b;--ate-highlight-bg: #854d0e;--ate-highlight-color: #fef08a;--ate-button-hover: #1e293b;--ate-button-active: #0f172a;--ate-menu-border: rgba(255, 255, 255, .1);--ate-menu-shadow: 0 20px 25px -5px rgba(0, 0, 0, .3), 0 10px 10px -5px rgba(0, 0, 0, .2);--ate-error-color: #f87171;--ate-error-bg: #450a0a;--ate-error-border: #7f1d1d;--ate-drag-background: var(--ate-surface-tertiary);--ate-drag-border-color: var(--ate-primary);--ate-blockquote-border-color: var(--ate-primary);--ate-toolbar-button-active-background: var(--ate-primary-light);--ate-toolbar-button-active-color: var(--ate-primary);--ate-button-hover: var(--ate-surface-tertiary);--ate-button-active: var(--ate-surface-secondary);--ate-scrollbar-thumb: var(--ate-surface-tertiary);--ate-scrollbar-thumb-hover: var(--ate-text-muted)}:host(.fill-container){display:block;height:100%}.ate-editor{border:var(--ate-border-width) solid var(--ate-border-color);border-radius:var(--ate-border-radius);background:var(--ate-background);overflow:visible;transition:border-color .2s ease;position:relative}:host(.floating-toolbar) .ate-editor{overflow:visible}:host(.fill-container) .ate-editor{display:flex;flex-direction:column;height:100%}:host(.fill-container) .ate-content-wrapper{flex:1;min-height:0}:host(.fill-container) .ate-content{flex:1;min-height:0;overflow-y:auto}.ate-editor:focus-within{border-color:var(--ate-focus-color)}.ate-content{min-height:var(--editor-min-height, 200px);height:var(--editor-height, auto);max-height:var(--editor-max-height, none);overflow-y:var(--editor-overflow, visible);outline:none;position:relative;scrollbar-width:thin;scrollbar-color:var(--ate-scrollbar-thumb) var(--ate-scrollbar-track)}:host(.is-disabled) .ate-content{cursor:not-allowed;opacity:.7;-webkit-user-select:none;user-select:none;pointer-events:none;background-color:var(--ate-surface-tertiary)}:host(.is-readonly) .ate-content{cursor:default;-webkit-user-select:text;user-select:text}:host(.is-readonly) .ate-content ::ng-deep .ate-link{cursor:pointer;pointer-events:auto}.ate-content::-webkit-scrollbar{width:var(--ate-scrollbar-width)}.ate-content-wrapper{position:relative;display:flex;flex-direction:column;min-height:0}.ate-content-wrapper .ate-content{flex:1}.ate-content::-webkit-scrollbar-track{background:var(--ate-scrollbar-track)}.ate-content::-webkit-scrollbar-thumb{background:var(--ate-scrollbar-thumb);border:3px solid transparent;background-clip:content-box;border-radius:10px}.ate-content::-webkit-scrollbar-thumb:hover{background:var(--ate-scrollbar-thumb-hover);background-clip:content-box}.ate-content.drag-over{background:var(--ate-drag-background);border:2px dashed var(--ate-drag-border-color)}.character-count{padding:6px 8px;font-size:12px;color:var(--ate-counter-color);text-align:right;border-top:1px solid var(--ate-counter-border-color);background:var(--ate-counter-background);transition:color .2s ease;border-bottom-left-radius:calc(var(--ate-border-radius) - var(--ate-border-width));border-bottom-right-radius:calc(var(--ate-border-radius) - var(--ate-border-width))}.character-count.limit-reached{color:var(--ate-error-color, #ef4444);font-weight:600}:host ::ng-deep .ProseMirror{padding:var(--ate-content-padding);outline:none;line-height:var(--ate-line-height);color:var(--ate-text-color);min-height:100%;height:100%;word-wrap:break-word;overflow-wrap:break-word}:host ::ng-deep .ProseMirror h1{font-size:2em;font-weight:700;margin-top:0;margin-bottom:.5em}:host ::ng-deep .ProseMirror h2{font-size:1.5em;font-weight:700;margin-top:1em;margin-bottom:.5em}:host ::ng-deep .ProseMirror h3{font-size:1.25em;font-weight:700;margin-top:1em;margin-bottom:.5em}:host ::ng-deep .ProseMirror p{margin:.5em 0}:host ::ng-deep .ProseMirror ul,:host ::ng-deep .ProseMirror ol{padding-left:2em;margin:.5em 0}:host ::ng-deep .ProseMirror blockquote{border-left:4px solid var(--ate-blockquote-border-color);margin:1em 0;background:var(--ate-blockquote-background);padding:.5em 1em;border-radius:0 4px 4px 0}:host ::ng-deep .ProseMirror code{background:var(--ate-code-background);color:var(--ate-code-color);border:1px solid var(--ate-code-border-color);padding:.15em .4em;border-radius:4px;font-family:JetBrains Mono,Fira Code,Monaco,Consolas,monospace;font-size:.85em;font-weight:500}:host ::ng-deep .ProseMirror pre{background:var(--ate-code-block-background);color:var(--ate-code-block-color);border:1px solid var(--ate-code-block-border-color);padding:1em;border-radius:var(--ate-border-radius);overflow-x:auto;margin:1em 0}:host ::ng-deep .ProseMirror pre code{background:none;color:inherit;border:none;padding:0}:host ::ng-deep .ProseMirror p.is-editor-empty:first-child:before{content:attr(data-placeholder);color:var(--ate-placeholder-color);pointer-events:none;float:left;height:0}:host ::ng-deep .ProseMirror[contenteditable=false]{pointer-events:none}:host ::ng-deep .ProseMirror[contenteditable=false] img{cursor:default;pointer-events:none}:host ::ng-deep .ProseMirror[contenteditable=false] img:hover{transform:none;box-shadow:0 2px 8px #0000001a}:host ::ng-deep .ProseMirror[contenteditable=false] img.ProseMirror-selectednode{outline:none}:host ::ng-deep .ProseMirror img{position:relative;display:inline-block;max-width:100%;height:auto;cursor:pointer;transition:all .2s ease;border:2px solid transparent;border-radius:var(--ate-image-border-radius)}:host ::ng-deep .ProseMirror img:hover{border-color:var(--ate-border-color);box-shadow:0 2px 4px #0000001a}:host ::ng-deep .ProseMirror img.ProseMirror-selectednode{border-color:var(--ate-image-selected-color);box-shadow:0 0 0 3px var(--ate-primary-light-alpha);transition:all .2s ease}:host ::ng-deep .ProseMirror .tiptap-image{max-width:100%;height:auto;border-radius:var(--ate-image-border-radius);box-shadow:0 4px 20px #00000014;margin:.5em 0;cursor:pointer;transition:all .3s cubic-bezier(.4,0,.2,1);display:block;filter:brightness(1) contrast(1)}:host ::ng-deep .ProseMirror .tiptap-image:hover{box-shadow:0 8px 30px #0000001f;filter:brightness(1.02) contrast(1.02)}:host ::ng-deep .ProseMirror .tiptap-image.ProseMirror-selectednode{outline:2px solid var(--ate-primary);outline-offset:2px;border-radius:var(--ate-image-border-radius);box-shadow:0 0 0 4px var(--ate-primary-light-alpha)}:host ::ng-deep .image-container{margin:.5em 0;text-align:center;border-radius:16px;overflow:hidden;transition:all .3s cubic-bezier(.4,0,.2,1)}:host ::ng-deep .image-container.image-align-left{text-align:left}:host ::ng-deep .image-container.image-align-center{text-align:center}:host ::ng-deep .image-container.image-align-right{text-align:right}:host ::ng-deep .image-container img{display:inline-block;max-width:100%;height:auto;border-radius:16px}:host ::ng-deep .resizable-image-container{position:relative;display:inline-block;margin:.5em 0}:host ::ng-deep .resize-controls{position:absolute;inset:0;pointer-events:none;z-index:1000}:host ::ng-deep .resize-handle{position:absolute;width:12px;height:12px;background:var(--ate-primary);border:2px solid var(--ate-surface);border-radius:50%;pointer-events:all;cursor:pointer;z-index:1001;transition:all .15s ease;box-shadow:0 2px 6px #0003}:host ::ng-deep .resize-handle:hover{background:var(--ate-primary);box-shadow:0 3px 8px #0000004d}:host ::ng-deep .resize-handle:active{background:var(--ate-primary)}:host ::ng-deep .resize-handle-n:hover,:host ::ng-deep .resize-handle-s:hover{transform:translate(-50%) scale(1.2)}:host ::ng-deep .resize-handle-w:hover,:host ::ng-deep .resize-handle-e:hover{transform:translateY(-50%) scale(1.2)}:host ::ng-deep .resize-handle-n:active,:host ::ng-deep .resize-handle-s:active{transform:translate(-50%) scale(.9)}:host ::ng-deep .resize-handle-w:active,:host ::ng-deep .resize-handle-e:active{transform:translateY(-50%) scale(.9)}:host ::ng-deep .resize-handle-nw:hover,:host ::ng-deep .resize-handle-ne:hover,:host ::ng-deep .resize-handle-sw:hover,:host ::ng-deep .resize-handle-se:hover{transform:scale(1.2)}:host ::ng-deep .resize-handle-nw:active,:host ::ng-deep .resize-handle-ne:active,:host ::ng-deep .resize-handle-sw:active,:host ::ng-deep .resize-handle-se:active{transform:scale(.9)}:host ::ng-deep .resize-handle-nw{top:0;left:-6px;cursor:nw-resize}:host ::ng-deep .resize-handle-n{top:0;left:50%;transform:translate(-50%);cursor:n-resize}:host ::ng-deep .resize-handle-ne{top:0;right:-6px;cursor:ne-resize}:host ::ng-deep .resize-handle-w{top:50%;left:-6px;transform:translateY(-50%);cursor:w-resize}:host ::ng-deep .resize-handle-e{top:50%;right:-6px;transform:translateY(-50%);cursor:e-resize}:host ::ng-deep .resize-handle-sw{bottom:0;left:-6px;cursor:sw-resize}:host ::ng-deep .resize-handle-s{bottom:0;left:50%;transform:translate(-50%);cursor:s-resize}:host ::ng-deep .resize-handle-se{bottom:0;right:-6px;cursor:se-resize}:host ::ng-deep body.resizing{-webkit-user-select:none;user-select:none;cursor:crosshair}:host ::ng-deep body.resizing .ProseMirror{pointer-events:none}:host ::ng-deep body.resizing .ProseMirror .tiptap-image{pointer-events:none}:host ::ng-deep .image-size-info{position:absolute;bottom:-20px;left:50%;transform:translate(-50%);background:#000c;color:#fff;padding:2px 6px;border-radius:3px;font-size:11px;white-space:nowrap;opacity:0;transition:opacity .2s ease}:host ::ng-deep .image-container:hover .image-size-info{opacity:1}:host ::ng-deep .ProseMirror table{border-collapse:separate;border-spacing:0;margin:0;table-layout:fixed;width:100%;border-radius:8px;overflow:hidden}:host ::ng-deep .ProseMirror table td,:host ::ng-deep .ProseMirror table th{border:none;border-right:1px solid var(--ate-table-border-color);border-bottom:1px solid var(--ate-table-border-color);box-sizing:border-box;min-width:1em;padding:8px 12px;position:relative;vertical-align:top;text-align:left}:host ::ng-deep .ProseMirror table td{background:var(--ate-table-cell-background)}:host ::ng-deep .ProseMirror table td:first-child,:host ::ng-deep .ProseMirror table th:first-child{border-left:1px solid var(--ate-table-border-color)}:host ::ng-deep .ProseMirror table tr:first-child td,:host ::ng-deep .ProseMirror table tr:first-child th{border-top:1px solid var(--ate-table-border-color)}:host ::ng-deep .ProseMirror table tr:first-child th:first-child{border-top-left-radius:8px}:host ::ng-deep .ProseMirror table tr:first-child th:last-child{border-top-right-radius:8px}:host ::ng-deep .ProseMirror table tr:last-child td:first-child{border-bottom-left-radius:8px}:host ::ng-deep .ProseMirror table tr:last-child td:last-child{border-bottom-right-radius:8px}:host ::ng-deep .ProseMirror table th{background:var(--ate-table-header-background);font-weight:600;color:var(--ate-table-header-color)}:host ::ng-deep .ProseMirror table .selectedCell:after{background:var(--ate-table-cell-selected-background);content:\"\";inset:0;pointer-events:none;position:absolute;z-index:2}:host ::ng-deep .ProseMirror table .column-resize-handle{position:absolute;right:-2px;top:0;bottom:0;width:4px;background-color:var(--ate-table-resize-handle-color);opacity:0;transition:opacity .2s ease}:host ::ng-deep .ProseMirror table:hover .column-resize-handle{opacity:1}:host ::ng-deep .ProseMirror table .column-resize-handle:hover{background-color:var(--ate-focus-color)}:host ::ng-deep .ProseMirror .tableWrapper{overflow-x:auto;margin:1em 0;border-radius:8px}:host ::ng-deep .ProseMirror .tableWrapper table{margin:0;border-radius:8px;min-width:600px;overflow:hidden}:host ::ng-deep .ProseMirror table p{margin:0}:host ::ng-deep .ProseMirror table tbody tr:hover td{background-color:var(--ate-table-row-hover-background)}\n"], dependencies: [{ kind: "component", type: AteToolbarComponent, selector: "ate-toolbar", inputs: ["editor", "config", "imageUpload", "floating"] }, { kind: "component", type: AteBubbleMenuComponent, selector: "ate-bubble-menu", inputs: ["config"] }, { kind: "component", type: AteImageBubbleMenuComponent, selector: "ate-image-bubble-menu", inputs: ["config", "imageUpload"] }, { kind: "component", type: AteTableBubbleMenuComponent, selector: "ate-table-bubble-menu", inputs: ["config"] }, { kind: "component", type: AteCellBubbleMenuComponent, selector: "ate-cell-bubble-menu", inputs: ["config"] }, { kind: "component", type: AteSlashCommandsComponent, selector: "ate-slash-commands", inputs: ["editor", "config"] }, { kind: "component", type: AteLinkBubbleMenuComponent, selector: "ate-link-bubble-menu" }, { kind: "component", type: AteColorBubbleMenuComponent, selector: "ate-color-bubble-menu" }, { kind: "component", type: AteEditToggleComponent, selector: "ate-edit-toggle", inputs: ["editable", "translations"], outputs: ["editToggle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
6318
6447
  }
6319
6448
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AngularTiptapEditorComponent, decorators: [{
6320
6449
  type: Component,
@@ -6431,7 +6560,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
6431
6560
 
6432
6561
  <!-- Counters -->
6433
6562
  @if (
6434
- finalEditable() && !mergedDisabled() && finalShowFooter() && (finalShowCharacterCount() || finalShowWordCount())
6563
+ finalEditable() &&
6564
+ !mergedDisabled() &&
6565
+ finalShowFooter() &&
6566
+ (finalShowCharacterCount() || finalShowWordCount())
6435
6567
  ) {
6436
6568
  <div
6437
6569
  class="character-count"