Repository: tinymce/tinymce
Pull request #10275: TINY-11981: table resizers inside the editor for inline mode
Author: login: ztomaszyk
Created: 2025-04-08T15:32:55Z | Merged: 2025-04-15T12:55:39Z

Related Ticket: TINY-11981

Description of Changes:
* Moved resize wire into the editor body for inline mode
* Removed unnecessary container insertion in the inline resize wire implementation
* Added a check to prevent bar destruction when hovering over resizer elements


Pre-checks:
* [x] Changelog entry added
* [x] Tests have been added (if applicable)
* [x] Branch prefixed with `feature/`, `hotfix/` or `spike/`

Review:
* [x] Milestone set
* [x] ~~Docs ticket created (if applicable)~~

GitHub issues (if applicable):

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **Bug Fixes**
  - Fixed an issue where table resizers were not visible in inline editing mode when a z-index was applied.

- **Refactor**
  - Streamlined the table resizing interactions to ensure a more stable and consistent editing experience.
  - Simplified event handling and logic related to resize bars in the editor.
  - Updated logic for determining resizer elements during mouse events.
  - Removed unnecessary functions and simplified the logic for handling resize bars directly within the editor body.
  - Enhanced type safety in event handling for resizing operations.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/.changes/unreleased/tinymce-TINY-11981-2025-04-11.yaml b/.changes/unreleased/tinymce-TINY-11981-2025-04-11.yaml
new file mode 100644
index 00000000000..0512cc03abe
--- /dev/null
+++ b/.changes/unreleased/tinymce-TINY-11981-2025-04-11.yaml
@@ -0,0 +1,6 @@
+project: tinymce
+kind: Fixed
+body: Table resizers are now visible when inline editor has a z-index property.
+time: 2025-04-11T17:03:57.705164+02:00
+custom:
+  Issue: TINY-11981
diff --git a/modules/snooker/src/demo/ts/ephox/snooker/demo/DetectDemo.ts b/modules/snooker/src/demo/ts/ephox/snooker/demo/DetectDemo.ts
index a0008d822db..2d7fdeda4b0 100644
--- a/modules/snooker/src/demo/ts/ephox/snooker/demo/DetectDemo.ts
+++ b/modules/snooker/src/demo/ts/ephox/snooker/demo/DetectDemo.ts
@@ -131,9 +131,9 @@ Ready.document(() => {
 
   const lazyTableSize = (table: SugarElement<HTMLTableElement>) => TableSize.getTableSize(table);
   const isResizable = Fun.always;
-  const ltrManager = TableResize.create(ResizeWire.body(tester, ltrs, isResizable), ResizeBehaviour.preserveTable(), lazyTableSize);
+  const ltrManager = TableResize.create(ResizeWire.body(tester, isResizable), ResizeBehaviour.preserveTable(), lazyTableSize);
   ltrManager.on();
-  const rtlManager = TableResize.create(ResizeWire.body(subject3, rtls, isResizable), ResizeBehaviour.preserveTable(), lazyTableSize);
+  const rtlManager = TableResize.create(ResizeWire.body(subject3, isResizable), ResizeBehaviour.preserveTable(), lazyTableSize);
   rtlManager.on();
 
   // For firefox.
diff --git a/modules/snooker/src/main/ts/ephox/snooker/api/ResizeWire.ts b/modules/snooker/src/main/ts/ephox/snooker/api/ResizeWire.ts
index 826c055fa9d..2eb1064d2da 100644
--- a/modules/snooker/src/main/ts/ephox/snooker/api/ResizeWire.ts
+++ b/modules/snooker/src/main/ts/ephox/snooker/api/ResizeWire.ts
@@ -43,22 +43,12 @@ const detached = (editable: SugarElement<Element>, chrome: SugarElement<Element>
   };
 };
 
-const body = (editable: SugarElement<Element>, chrome: SugarElement<Element>, isResizable: ResizeCallback): ResizeWire => {
+const body = (editable: SugarElement<Element>, isResizable: ResizeCallback): ResizeWire => {
   return {
-    parent: Fun.constant(chrome),
-    view: Fun.constant(editable),
-    dragContainer: Fun.constant(chrome),
-    origin: Fun.constant(SugarPosition(0, 0)),
-    isResizable
-  };
-};
-
-const scrollable = (editable: SugarElement<Element>, chrome: SugarElement<Element>, dragContainer: SugarElement<Element>, isResizable: ResizeCallback): ResizeWire => {
-  return {
-    parent: Fun.constant(chrome),
+    parent: Fun.constant(editable),
     view: Fun.constant(editable),
-    dragContainer: Fun.constant(dragContainer),
-    origin: () => SugarLocation.absolute(chrome),
+    dragContainer: Fun.constant(editable),
+    origin: () => SugarLocation.absolute(editable),
     isResizable
   };
 };
@@ -66,7 +56,6 @@ const scrollable = (editable: SugarElement<Element>, chrome: SugarElement<Elemen
 export const ResizeWire = {
   only,
   detached,
-  body,
-  scrollable
+  body
 };
 
diff --git a/modules/snooker/src/main/ts/ephox/snooker/resize/Bar.ts b/modules/snooker/src/main/ts/ephox/snooker/resize/Bar.ts
index c0309accbc3..444b274964d 100644
--- a/modules/snooker/src/main/ts/ephox/snooker/resize/Bar.ts
+++ b/modules/snooker/src/main/ts/ephox/snooker/resize/Bar.ts
@@ -10,7 +10,7 @@ const col = (column: number, x: number, y: number, w: number, h: number): SugarE
     width: w + 'px'
   });
 
-  Attribute.setAll(bar, { 'data-column': column, 'role': 'presentation' });
+  Attribute.setAll(bar, { 'data-mce-bogus': 'all', 'data-column': column, 'role': 'presentation' });
   return bar;
 };
 
@@ -24,7 +24,7 @@ const row = (r: number, x: number, y: number, w: number, h: number): SugarElemen
     width: w + 'px'
   });
 
-  Attribute.setAll(bar, { 'data-row': r, 'role': 'presentation' });
+  Attribute.setAll(bar, { 'data-mce-bogus': 'all', 'data-row': r, 'role': 'presentation' });
   return bar;
 };
 
diff --git a/modules/snooker/src/main/ts/ephox/snooker/resize/BarManager.ts b/modules/snooker/src/main/ts/ephox/snooker/resize/BarManager.ts
index 1fea3a40e65..299e5f2d07d 100644
--- a/modules/snooker/src/main/ts/ephox/snooker/resize/BarManager.ts
+++ b/modules/snooker/src/main/ts/ephox/snooker/resize/BarManager.ts
@@ -124,16 +124,21 @@ export const BarManager = (wire: ResizeWire): BarManager => {
   const findClosestEditableTable = (target: SugarElement<Node>): Optional<SugarElement<HTMLTableElement>> =>
     SelectorFind.closest<HTMLTableElement>(target, 'table', isRoot).filter(ContentEditable.isEditable);
 
+  const isResizer = (target: SugarElement<Element>): boolean =>
+    Class.has(target, 'ephox-snooker-resizer-bar') || Class.has(target, 'ephox-dragster-blocker');
+
   /* mouseover on table: When the mouse moves within the CONTENT AREA (NOT THE TABLE), refresh the bars. */
-  const mouseover = DomEvent.bind(wire.view(), 'mouseover', (event) => {
+  const mouseover = DomEvent.bind<MouseEvent, Element>(wire.view(), 'mouseover', (event) => {
     findClosestEditableTable(event.target).fold(
       () => {
         /*
         * mouseout is not reliable within ContentEditable, so for all other mouseover events we clear bars.
         * This is fairly safe to do frequently; it's a single querySelectorAll() on the content and Arr.map on the result.
         * If we _really_ need to optimise it further, we can start caching the bar references in the wire somehow.
+        *
+        * Because the resizers were moved into the editor for inline mode, we need to check if the event target is not a resizer.
         */
-        if (SugarBody.inBody(event.target)) {
+        if (SugarBody.inBody(event.target) && !isResizer(event.target)) {
           Bars.destroy(wire);
         }
       },
diff --git a/modules/tinymce/src/core/test/ts/browser/init/InitEditorNoThemeInlineTest.ts b/modules/tinymce/src/core/test/ts/browser/init/InitEditorNoThemeInlineTest.ts
index 76e197d7ca4..99493b31b69 100644
--- a/modules/tinymce/src/core/test/ts/browser/init/InitEditorNoThemeInlineTest.ts
+++ b/modules/tinymce/src/core/test/ts/browser/init/InitEditorNoThemeInlineTest.ts
@@ -1,4 +1,4 @@
-import { ApproxStructure, Assertions } from '@ephox/agar';
+import { Assertions } from '@ephox/agar';
 import { describe, it } from '@ephox/bedrock-client';
 import { SelectorFind, SugarBody, Traverse } from '@ephox/sugar';
 import { TinyAssertions, TinyDom, TinyHooks } from '@ephox/wrap-mcagar';
@@ -26,17 +26,13 @@ describe('browser.tinymce.core.init.InitEditorNoThemeInlineTest', () => {
     const editor = hook.editor();
     const body = SugarBody.body();
     const targetElement = SelectorFind.descendant(body, '#' + editor.id).getOrDie('No elm');
-    const nextElement = Traverse.nextSibling(targetElement).getOrDie('Should be an element after the target');
 
     // TODO FIXME this seems like an odd exception
     assert.isNull(editor.getContainer(), 'Should be null since inline without a theme does not set editorContainer');
     Assertions.assertDomEq('Should be expected editor body element', targetElement, TinyDom.body(editor));
     Assertions.assertDomEq('Should be expected editor target element', targetElement, TinyDom.targetElement(editor));
     Assertions.assertDomEq('Editor.contentAreaContainer should equal target element', targetElement, TinyDom.contentAreaContainer(editor));
-    // The only element that should be after the target is the TableResizeHandler TableWire div element
-    assert.lengthOf(Traverse.nextSiblings(targetElement), 1, 'Should only be one element after target');
-    // test with 5px
-    const resizeDivStructure = ApproxStructure.fromHtml('<div style="position: static; height: 0px; width: 0px; padding: 0px; margin: 0px; border: 0px;"></div>');
-    Assertions.assertStructure('Sibling element should be a resize div', resizeDivStructure, nextElement);
+
+    assert.lengthOf(Traverse.nextSiblings(targetElement), 0, 'There should be no element added next to inline editor');
   });
 });
diff --git a/modules/tinymce/src/models/dom/main/ts/table/api/TableResizeHandler.ts b/modules/tinymce/src/models/dom/main/ts/table/api/TableResizeHandler.ts
index 0737c874b2d..c774f201a56 100644
--- a/modules/tinymce/src/models/dom/main/ts/table/api/TableResizeHandler.ts
+++ b/modules/tinymce/src/models/dom/main/ts/table/api/TableResizeHandler.ts
@@ -121,10 +121,6 @@ export const TableResizeHandler = (editor: Editor): TableResizeHandler => {
     tableResize.on((sz) => {
       sz.destroy();
     });
-
-    resizeWire.on((w) => {
-      TableWire.remove(editor, w);
-    });
   };
 
   editor.on('init', () => {
diff --git a/modules/tinymce/src/models/dom/main/ts/table/core/TableWire.ts b/modules/tinymce/src/models/dom/main/ts/table/core/TableWire.ts
index 7cc82edcb40..d682bb715a1 100644
--- a/modules/tinymce/src/models/dom/main/ts/table/core/TableWire.ts
+++ b/modules/tinymce/src/models/dom/main/ts/table/core/TableWire.ts
@@ -1,59 +1,14 @@
-import { Id } from '@ephox/katamari';
 import { ResizeWire } from '@ephox/snooker';
-import { Attribute, Css, Insert, Remove, SugarBody, SugarElement } from '@ephox/sugar';
+import { SugarElement } from '@ephox/sugar';
 
 import Editor from 'tinymce/core/api/Editor';
 
-import * as Options from '../api/Options';
-
-type PositionType = 'relative' | 'static';
-
-const createContainer = (position: PositionType): SugarElement<HTMLDivElement> => {
-  const id = Id.generate('resizer-container');
-  const container = SugarElement.fromTag('div');
-
-  Attribute.set(container, 'id', id);
-  Css.setAll(container, {
-    position,
-    height: '0',
-    width: '0',
-    padding: '0',
-    margin: '0',
-    border: '0'
-  });
-
-  return container;
-};
-
-const getInlineResizeWire = (editor: Editor, isResizable: (elm: SugarElement<Element>) => boolean): ResizeWire => {
-  const isSplitUiMode = Options.isSplitUiMode(editor);
-  const editorBody = SugarElement.fromDom(editor.getBody());
-  const container = createContainer(isSplitUiMode ? 'relative' : 'static');
-  const body = SugarBody.body();
-
-  if (isSplitUiMode) {
-    Insert.after(editorBody, container);
-    return ResizeWire.scrollable(editorBody, container, body, isResizable);
-  }
-
-  Insert.append(body, container);
-  return ResizeWire.body(editorBody, container, isResizable);
-};
-
 const get = (editor: Editor, isResizable: (elm: SugarElement<Element>) => boolean): ResizeWire => {
-  if (editor.inline) {
-    return getInlineResizeWire(editor, isResizable);
-  }
-  return ResizeWire.only(SugarElement.fromDom(editor.getDoc()), isResizable);
-};
+  const editorBody = SugarElement.fromDom(editor.getBody());
 
-const remove = (editor: Editor, wire: ResizeWire): void => {
-  if (editor.inline) {
-    Remove.remove(wire.parent());
-  }
+  return ResizeWire.body(editorBody, isResizable);
 };
 
 export {
-  get,
-  remove
+  get
 };
diff --git a/modules/tinymce/src/models/dom/test/ts/browser/table/ResizeTableTest.ts b/modules/tinymce/src/models/dom/test/ts/browser/table/ResizeTableTest.ts
index a30322be238..aab478d09ff 100644
--- a/modules/tinymce/src/models/dom/test/ts/browser/table/ResizeTableTest.ts
+++ b/modules/tinymce/src/models/dom/test/ts/browser/table/ResizeTableTest.ts
@@ -3,7 +3,7 @@ import { beforeEach, context, describe, it } from '@ephox/bedrock-client';
 import { Arr, Cell, Fun, Strings } from '@ephox/katamari';
 import { PlatformDetection } from '@ephox/sand';
 import { TableGridSize } from '@ephox/snooker';
-import { Class, Css, Height, Html, Insert, InsertAll, Remove, SelectorExists, SelectorFilter, SelectorFind, SugarBody, SugarElement, Traverse } from '@ephox/sugar';
+import { Class, Css, Height, Html, Insert, InsertAll, Remove, SelectorExists, SelectorFilter, SelectorFind, SugarBody, SugarElement } from '@ephox/sugar';
 import { TinyDom, TinyHooks } from '@ephox/wrap-mcagar';
 import { assert } from 'chai';
 
@@ -1180,52 +1180,64 @@ describe('browser.tinymce.models.dom.table.ResizeTableTest', () => {
     );
   };
 
-  const getResizeBarsContainer = (editor: Editor) => SelectorFind.sibling(TinyDom.body(editor), '[id^=resizer-container]').getOrDie('Resizer container element not found as a sibling to the editor body');
-
-  const assertContainerAndBarsExist = (editor: Editor) => {
-    const container = getResizeBarsContainer(editor);
-    const resizeBars = Traverse.children(container);
-    assert.isAbove(resizeBars.length, 0, 'Should have resize bars within the container');
+  const assertBarsExistInTheEditorBody = (editor: Editor) => {
+    const resizeBars = SelectorFilter.children(TinyDom.body(editor), '.ephox-snooker-resizer-bar');
+    assert.isAbove(resizeBars.length, 0, 'Should have resize bars within the editor body');
   };
 
-  const assertBarsNotExistInContainer = (editor: Editor) => {
-    const container = getResizeBarsContainer(editor);
-    const resizeBars = Traverse.children(container);
-    assert.equal(resizeBars.length, 0, 'Resize bars should not exist in the container');
+  const assertBarsNotExistInTheEditorBody = (editor: Editor) => {
+    const resizeBars = SelectorFilter.children(TinyDom.body(editor), '.ephox-snooker-resizer-bar');
+    assert.equal(resizeBars.length, 0, 'Resize bars should not exist in the editor');
   };
 
   const assertResizerPosition = (editor: Editor, table: HTMLTableElement) => {
-    const container = getResizeBarsContainer(editor);
-    const containerRect = container.dom.getBoundingClientRect();
+    const editorBody = TinyDom.body(editor);
+    const editorBodyRect = editorBody.dom.getBoundingClientRect();
 
     Arr.each(table.rows, (row: HTMLTableRowElement, rowIndex: number) => {
-      const resizer = SelectorFind.descendant<HTMLElement>(container, `.ephox-snooker-resizer-rows[data-row="${rowIndex}"]`).getOrDie('Resizer not found');
+      const resizer = SelectorFind.descendant<HTMLElement>(editorBody, `.ephox-snooker-resizer-rows[data-row="${rowIndex}"]`).getOrDie('Resizer not found');
       const rowRect = row.getBoundingClientRect();
       const resizerHeight = Height.get(resizer);
 
-      const borderSpacing = parseFloat(Css.get(SugarElement.fromDom(table), 'border-spacing'));
-      const isLastRow = rowIndex === table.rows.length - 1;
-
-      const distanceFromBottom = containerRect.bottom - rowRect.bottom;
-      const expectedTop = -(distanceFromBottom + resizerHeight / 2 - (isLastRow ? 0 : borderSpacing));
+      const distanceFromTop = rowRect.top + rowRect.height - editorBodyRect.top;
+      const expectedTop = distanceFromTop - resizerHeight / 2;
 
       const actualResizerTop = parseFloat(Strings.removeTrailing(Css.get(resizer, 'top'), 'px'));
       assert.approximately(actualResizerTop, expectedTop, 2, `Resizer position mismatch for row ${rowIndex} - Expected: ${expectedTop}, Actual: ${actualResizerTop}`);
     });
   };
 
-  context('Location of table resize bar handler container', () => {
+  const clickOnResizer = (editor: Editor, rowIndex: number) => {
+    const editorBody = TinyDom.body(editor);
+    const resizer = SelectorFind.descendant<HTMLElement>(editorBody, `.ephox-snooker-resizer-rows[data-row="${rowIndex}"]`).getOrDie('Resizer not found');
+    Mouse.trueClick(resizer);
+  };
+
+  context('Location of table resize bars', () => {
     context('iframe mode', () => {
       const hook = TinyHooks.bddSetup<Editor>(defaultSettings, [], true);
 
-      it('TINY-11215: The resize bar handler container should be inside the iframe', async () => {
+      it('TINY-11215: The resize bars should be inside the the editor body', async () => {
         const editor = hook.editor();
         TableTestUtils.insertRaw(editor, percentTable);
         const table = editor.dom.select('table')[0];
         await hoverOnTable(editor, table, 'row', 0);
-        const resizerBars = SelectorFilter.siblings(TinyDom.body(editor), '.ephox-snooker-resizer-bar');
+        const resizerBars = SelectorFilter.children(TinyDom.body(editor), '.ephox-snooker-resizer-bar');
         assert.isAbove(resizerBars.length, 0, 'Resizer bars should exist');
       });
+
+      it('TINY-11981: Hovering non table element should remove the table resize bars', async () => {
+        const editor = hook.editor();
+        editor.setContent(`<p>This is non table element</p><table style="border-collapse: collapse; width: 100%; height: 178.586px;" border="1"><colgroup><col style="width: 33.3112%;"><col style="width: 33.3112%;"><col style="width: 33.3112%;"></colgroup>
+          <tbody><tr style="height: 36.1953px;"><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr><tr style="height: 56.1953px;">
+          <td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr>
+          <tr style="height: 86.1953px;"><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr></tbody></table>`);
+        const table = editor.dom.select('table')[0];
+        await hoverOnTable(editor, table, 'row', 0);
+        assertBarsExistInTheEditorBody(editor);
+        hoverOnElement(editor, 'p');
+        assertBarsNotExistInTheEditorBody(editor);
+      });
     });
 
     context('inline mode', () => {
@@ -1242,11 +1254,11 @@ describe('browser.tinymce.models.dom.table.ResizeTableTest', () => {
         };
       }, [], true);
 
-      it('TINY-11215: The resize bar handler container should be adjacent to the editor body', async () => {
+      it('TINY-11215: The resize bars should be inside the editor body', async () => {
         const editor = hook.editor();
         const table = editor.dom.select('table')[0];
         await hoverOnTable(editor, table, 'row', 0);
-        assertContainerAndBarsExist(editor);
+        assertBarsExistInTheEditorBody(editor);
       });
 
       it('TINY-11215: The resize bar wires should be at the correct location', async () => {
@@ -1254,6 +1266,8 @@ describe('browser.tinymce.models.dom.table.ResizeTableTest', () => {
         const table = editor.dom.select('table')[0];
         await hoverOnTable(editor, table, 'row', 0);
         assertResizerPosition(editor, table);
+        clickOnResizer(editor, 0);
+        assertResizerPosition(editor, table);
       });
 
       it('TINY-11215: The resize bar wires should be at the correct location with resized rows', async () => {
@@ -1266,6 +1280,19 @@ describe('browser.tinymce.models.dom.table.ResizeTableTest', () => {
         await hoverOnTable(editor, table, 'row', 0);
         assertResizerPosition(editor, table);
       });
+
+      it('TINY-11981: Hovering non table element should remove the table resize bars', async () => {
+        const editor = hook.editor();
+        editor.setContent(`<p>This is non table element</p><table style="border-collapse: collapse; width: 100%; height: 178.586px;" border="1"><colgroup><col style="width: 33.3112%;"><col style="width: 33.3112%;"><col style="width: 33.3112%;"></colgroup>
+          <tbody><tr style="height: 36.1953px;"><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr><tr style="height: 56.1953px;">
+          <td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr>
+          <tr style="height: 86.1953px;"><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr></tbody></table>`);
+        const table = editor.dom.select('table')[0];
+        await hoverOnTable(editor, table, 'row', 0);
+        assertBarsExistInTheEditorBody(editor);
+        hoverOnElement(editor, 'p');
+        assertBarsNotExistInTheEditorBody(editor);
+      });
     });
 
     context('inline mode with scrollable container', () => {
@@ -1319,18 +1346,20 @@ describe('browser.tinymce.models.dom.table.ResizeTableTest', () => {
         };
       }, [], true);
 
-      it('TINY-11215: The resize bar handler container should be adjacent to the editor body', async () => {
+      it('TINY-11215: The resize bars should be inside the editor body', async () => {
         const editor = hook.editor();
         const table = editor.dom.select('table')[0];
         await hoverOnTable(editor, table, 'row', 0);
-        assertContainerAndBarsExist(editor);
+        assertBarsExistInTheEditorBody(editor);
       });
 
       it('TINY-11215: Resize bar handler should render at the correct location, close to the bottom of tr', async () => {
         const editor = hook.editor();
         const table = editor.dom.select('table')[0];
         await hoverOnTable(editor, table, 'row', 0);
-        assertContainerAndBarsExist(editor);
+        assertBarsExistInTheEditorBody(editor);
+        assertResizerPosition(editor, table);
+        clickOnResizer(editor, 0);
         assertResizerPosition(editor, table);
       });
 
@@ -1338,21 +1367,21 @@ describe('browser.tinymce.models.dom.table.ResizeTableTest', () => {
         const editor = hook.editor();
         const table = editor.dom.select('table')[0];
         await hoverOnTable(editor, table, 'row', 0);
-        assertContainerAndBarsExist(editor);
+        assertBarsExistInTheEditorBody(editor);
         const scrollable = SelectorFind.descendant(SugarBody.body(), '.scrollable').getOrDie();
         scrollable.dom.scrollBy(0, scrollable.dom.scrollHeight / 2);
         assertResizerPosition(editor, table);
         await hoverOnTable(editor, table, 'row', 0);
       });
 
-      it('TINY-11215: Hovering non table element should remove the table resize bar container', async () => {
+      it('TINY-11215: Hovering non table element should remove the table resize bars', async () => {
         const editor = hook.editor();
         editor.resetContent();
         const table = editor.dom.select('table')[0];
         await hoverOnTable(editor, table, 'row', 0);
-        assertContainerAndBarsExist(editor);
+        assertBarsExistInTheEditorBody(editor);
         hoverOnElement(editor, 'p');
-        assertBarsNotExistInContainer(editor);
+        assertBarsNotExistInTheEditorBody(editor);
       });
 
       it('TINY-11215: Table resize wires should still be attached when scrolling with multiple tables', async () => {
@@ -1364,7 +1393,7 @@ describe('browser.tinymce.models.dom.table.ResizeTableTest', () => {
 
         const table = editor.dom.select('table')[0];
         await hoverOnTable(editor, table, 'row', 0);
-        assertContainerAndBarsExist(editor);
+        assertBarsExistInTheEditorBody(editor);
         assertResizerPosition(editor, table);
 
         scrollable.dom.scrollTo(0, scrollable.dom.scrollHeight / 6);
@@ -1382,4 +1411,20 @@ describe('browser.tinymce.models.dom.table.ResizeTableTest', () => {
       });
     });
   });
+
+  context('Table resizers as part of the content', () => {
+    const hook = TinyHooks.bddSetup<Editor>(defaultSettings, [], true);
+
+    it('TINY-11981: getContent should return content without resize bars', async () => {
+      const insertTable = '<table style="width: 200px;"><tbody><tr><td>Content</td><td>Content</td></tr></tbody></table>';
+      const editor = hook.editor();
+      editor.setContent(insertTable);
+      const table = editor.dom.select('table')[0];
+      await hoverOnTable(editor, table, 'row', 0);
+      const resizerBars = SelectorFilter.children(TinyDom.body(editor), '.ephox-snooker-resizer-bar');
+      assert.isAbove(resizerBars.length, 0, 'Resizer bars should exist');
+      const content = editor.getContent();
+      assert.equal(content, insertTable, 'Content should not include resize bars');
+    });
+  });
 });


Reviews:
user: login: TheSpyder
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T23:47:24Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>♻️ Duplicate comments (2)</summary><blockquote>

<details>
<summary>modules/snooker/src/main/ts/ephox/snooker/resize/BarManager.ts (2)</summary><blockquote>

`127-129`: **Consider using Sugar's `Class.has` method for consistency.**

The implementation works correctly, but for consistency with the codebase, consider using the `Class.has` method from Sugar instead of direct DOM classList access.


```diff
-  const isResizer = (target: SugarElement<Element>): boolean =>
-    target.dom.classList.contains('ephox-snooker-resizer-bar') || target.dom.classList.contains('ephox-dragster-blocker');
+  const isResizer = (target: SugarElement<Element>): boolean =>
+    Class.has(target, 'ephox-snooker-resizer-bar') || Class.has(target, 'ephox-dragster-blocker');
```

As mentioned in a previous review, you could also use `Classes.hasAny(target, ['ephox-snooker-resizer-bar', 'ephox-dragster-blocker'])` but that might be overkill for just two classes.

---

`141-141`: **Excellent fix to prevent premature bar destruction.**

This change correctly implements the check to prevent destroying the resizing bars when hovering over resizer elements. This is crucial for the table resizing functionality to work properly in inline mode.

As mentioned in a previous review, is there a parent element we could check instead of using class list checks? This might provide a more robust solution in case the class names change in the future.

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>modules/tinymce/src/models/dom/main/ts/table/core/TableWire.ts (1)</summary><blockquote>

`13-13`: **Consider renaming to be more descriptive.**

While exporting `get` works, a more explicit name can improve readability:

```diff
-export {
-  get
-};
+export {
+  get as getTableWire
+};
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 34ceb85f44d4f09f6133df3c180623f395c1e0ca and ec6e41e6be504166adbb956ec3702f9405703631.

</details>

<details>
<summary>📒 Files selected for processing (6)</summary>

* `modules/snooker/src/demo/ts/ephox/snooker/demo/DetectDemo.ts` (1 hunks)
* `modules/snooker/src/main/ts/ephox/snooker/api/ResizeWire.ts` (1 hunks)
* `modules/snooker/src/main/ts/ephox/snooker/resize/Bar.ts` (2 hunks)
* `modules/snooker/src/main/ts/ephox/snooker/resize/BarManager.ts` (2 hunks)
* `modules/tinymce/src/models/dom/main/ts/table/api/TableResizeHandler.ts` (0 hunks)
* `modules/tinymce/src/models/dom/main/ts/table/core/TableWire.ts` (1 hunks)

</details>

<details>
<summary>💤 Files with no reviewable changes (1)</summary>

* modules/tinymce/src/models/dom/main/ts/table/api/TableResizeHandler.ts

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (2)</summary>

* modules/snooker/src/main/ts/ephox/snooker/api/ResizeWire.ts
* modules/snooker/src/demo/ts/ephox/snooker/demo/DetectDemo.ts

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>modules/snooker/src/main/ts/ephox/snooker/resize/Bar.ts (1)</summary><blockquote>

<details>
<summary>modules/tinymce/src/core/main/ts/schema/SchemaTypes.ts (1)</summary>

* `Attribute` (31-36)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (3)</summary>

* GitHub Check: task-list-completed
* GitHub Check: Analyze (javascript)
* GitHub Check: task-list-completed

</details>

<details>
<summary>🔇 Additional comments (5)</summary><blockquote>

<details>
<summary>modules/snooker/src/main/ts/ephox/snooker/resize/Bar.ts (2)</summary>

`13-13`: **Good addition of the `data-mce-bogus` attribute.**

Adding `'data-mce-bogus': 'all'` to the column resizer bar is an effective way to ensure TinyMCE treats these elements as non-editable content. This helps prevent unintended interactions with the resizer bars during content editing, which aligns well with improving table resizers in inline mode.

---

`27-27`: **Good addition of the `data-mce-bogus` attribute.**

Similarly, adding `'data-mce-bogus': 'all'` to the row resizer bar ensures consistent behavior with the column resizers. This change helps maintain a clean editing experience by marking these UI elements appropriately for TinyMCE.

</details>
<details>
<summary>modules/snooker/src/main/ts/ephox/snooker/resize/BarManager.ts (1)</summary>

`138-140`: **Good documentation of the inline mode edge case.**

The added comments clearly explain why this check is needed specifically for inline mode. This helps future developers understand the reasoning behind the implementation.

</details>
<details>
<summary>modules/tinymce/src/models/dom/main/ts/table/core/TableWire.ts (2)</summary>

`2-2`: **Import usage looks good.**

Adding `SugarElement` from `@ephox/sugar` is appropriate for wrapping the DOM element.

---

`7-9`: <details>
<summary>✅ Verification successful</summary>

**Potential null safety check.**

In practice, `editor.getBody()` should never be null or undefined once the editor is rendered. However, consider verifying that the editor has fully initialized before calling `SugarElement.fromDom(editor.getBody())`.  

Below is a script to search for usage patterns of `editor.getBody()` across the codebase, ensuring it's always safe to call:

---

🏁 Script executed:

```shell
#!/bin/bash
# Description: Find all references to editor.getBody() and print context lines
rg -A 5 "editor\.getBody\(\)"
```

Length of output: 69935

---



</details>

**Null Safety Usage is Consistent**

After reviewing the codebase, it’s clear that using `editor.getBody()` without an explicit null check is standard practice. The editor’s API guarantees a valid body when accessed after initialization, which aligns with how it’s used throughout the repository. No additional null safety check is needed in this case.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-10T19:24:16Z
user: login: spocke
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-11T13:36:51Z
user: login: ztomaszyk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T14:51:37Z
user: login: ztomaszyk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T14:52:46Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>modules/tinymce/src/plugins/table/demo/html/demo.html (1)</summary><blockquote>

`50-273`: **Consider cleaning up commented code in a future PR**

While keeping the previous implementation as reference is understandable during development, consider cleaning up this commented code once the new implementation is fully tested and proven stable. This would improve code maintainability.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between ba8e78f8e34afc0c0a1c96ab33626f5eef0fb455 and d2ff899279898776ab84c7d199551366d21428ca.

</details>

<details>
<summary>📒 Files selected for processing (5)</summary>

* `.changes/unreleased/tinymce-TINY-11981-2025-04-11.yaml` (1 hunks)
* `modules/tinymce/src/plugins/searchreplace/demo/html/demo.html` (1 hunks)
* `modules/tinymce/src/plugins/searchreplace/demo/ts/demo/Demo.ts` (1 hunks)
* `modules/tinymce/src/plugins/table/demo/html/demo.html` (2 hunks)
* `modules/tinymce/src/plugins/table/demo/ts/demo/Demo.ts` (2 hunks)

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (2)</summary>

* modules/tinymce/src/plugins/searchreplace/demo/html/demo.html
* .changes/unreleased/tinymce-TINY-11981-2025-04-11.yaml

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (3)</summary>

* GitHub Check: task-list-completed
* GitHub Check: Analyze (javascript)
* GitHub Check: task-list-completed

</details>

<details>
<summary>🔇 Additional comments (4)</summary><blockquote>

<details>
<summary>modules/tinymce/src/plugins/searchreplace/demo/ts/demo/Demo.ts (1)</summary>

`16-26`: **LGTM: Added inline mode initialization**

The addition of a second TinyMCE initialization with `inline: true` properly demonstrates the searchreplace plugin functionality in inline editing mode. This complements the existing textarea initialization, allowing users to test both editing modes.

</details>
<details>
<summary>modules/tinymce/src/plugins/table/demo/ts/demo/Demo.ts (2)</summary>

`8-9`: **LGTM: Enabled inline mode**

Adding the `inline: true` property correctly enables inline editing mode for div elements, which supports the PR objective of implementing table resizing within inline mode.

---

`45-81`: **LGTM: Added standard mode initialization**

Adding a separate initialization for textarea elements provides a good comparison between inline and standard editing modes. This setup allows proper testing of table resizing functionality in both contexts, which is essential for validating the PR's objectives.

</details>
<details>
<summary>modules/tinymce/src/plugins/table/demo/html/demo.html (1)</summary>

`10-49`: **LGTM: Added demonstration environment for inline editing**

The new structure with a scrollable container and properly styled content provides an excellent test environment for table resizing in inline mode. The combination of paragraphs and a table with varying content length creates realistic scenarios for testing the resize functionality.

The scrollable container is particularly useful for testing how table resizers behave when the editor content is within a scrollable area - an important consideration for the PR's focus on improving table resizers in inline mode.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-11T15:07:45Z
user: login: spocke
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-14T08:27:25Z
user: login: TheSpyder
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-14T11:35:21Z
user: login: github-advanced-security[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-14T11:40:45Z
user: login: ztomaszyk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T11:48:12Z

Review comments:
diff_hunk: @@ -124,6 +124,9 @@ export const BarManager = (wire: ResizeWire): BarManager => {
   const findClosestEditableTable = (target: SugarElement<Node>): Optional<SugarElement<HTMLTableElement>> =>
     SelectorFind.closest<HTMLTableElement>(target, 'table', isRoot).filter(ContentEditable.isEditable);
 
+  const isResizer = (target: SugarElement<Element>): boolean =>
+    target.dom.classList.contains('ephox-snooker-resizer-bar') || target.dom.classList.contains('ephox-dragster-blocker');
path: modules/snooker/src/main/ts/ephox/snooker/resize/BarManager.ts
user: login: TheSpyder
body: ```suggestion
    Class.has(target, 'ephox-snooker-resizer-bar') || Class.has(target, 'ephox-dragster-blocker');
```

Or you could do `Classes.hasAny(target, ['ephox-snooker-resizer-bar', 'ephox-dragster-blocker'])` but that's probably not worth it for only two.
created_at: 2025-04-08T23:42:40Z
author_association: MEMBER
diff_hunk: @@ -132,8 +135,10 @@ export const BarManager = (wire: ResizeWire): BarManager => {
         * mouseout is not reliable within ContentEditable, so for all other mouseover events we clear bars.
         * This is fairly safe to do frequently; it's a single querySelectorAll() on the content and Arr.map on the result.
         * If we _really_ need to optimise it further, we can start caching the bar references in the wire somehow.
+        *
+        * Because the resizers were moved into the editor for inline mode, we need to check if the event target is not a resizer.
         */
-        if (SugarBody.inBody(event.target)) {
+        if (SugarBody.inBody(event.target) && !isResizer(event.target as SugarElement)) {
path: modules/snooker/src/main/ts/ephox/snooker/resize/BarManager.ts
user: login: TheSpyder
body: Is there a parent element we could check the element is inside instead of using class list checks?
created_at: 2025-04-08T23:44:32Z
author_association: MEMBER
diff_hunk: @@ -36,8 +36,7 @@ const getInlineResizeWire = (editor: Editor, isResizable: (elm: SugarElement<Ele
     return ResizeWire.scrollable(editorBody, container, body, isResizable);
   }
 
-  Insert.append(body, container);
-  return ResizeWire.body(editorBody, container, isResizable);
+  return ResizeWire.body(editorBody, isResizable);
path: modules/tinymce/src/models/dom/main/ts/table/core/TableWire.ts
user: login: TheSpyder
body: Shouldn't this apply to the split ui scrollable as well? Do we even need a split ui for inline mode if it's defaulting to inside the content?
created_at: 2025-04-08T23:47:12Z
author_association: MEMBER
diff_hunk: @@ -132,8 +135,10 @@ export const BarManager = (wire: ResizeWire): BarManager => {
         * mouseout is not reliable within ContentEditable, so for all other mouseover events we clear bars.
         * This is fairly safe to do frequently; it's a single querySelectorAll() on the content and Arr.map on the result.
         * If we _really_ need to optimise it further, we can start caching the bar references in the wire somehow.
+        *
+        * Because the resizers were moved into the editor for inline mode, we need to check if the event target is not a resizer.
         */
-        if (SugarBody.inBody(event.target)) {
+        if (SugarBody.inBody(event.target) && !isResizer(event.target as SugarElement)) {
path: modules/snooker/src/main/ts/ephox/snooker/resize/BarManager.ts
user: login: spocke
body: You could avoid the magic cast here by instead doing `DomEvent.bind<MouseEvent, Element>` we know that it's a mouse event and we know that the nodes it could find could only be instances of Element. `event.target as SugarElement` makes it a `SugarElement<any>`.
created_at: 2025-04-11T13:36:50Z
author_association: MEMBER
diff_hunk: @@ -132,8 +135,10 @@ export const BarManager = (wire: ResizeWire): BarManager => {
         * mouseout is not reliable within ContentEditable, so for all other mouseover events we clear bars.
         * This is fairly safe to do frequently; it's a single querySelectorAll() on the content and Arr.map on the result.
         * If we _really_ need to optimise it further, we can start caching the bar references in the wire somehow.
+        *
+        * Because the resizers were moved into the editor for inline mode, we need to check if the event target is not a resizer.
         */
-        if (SugarBody.inBody(event.target)) {
+        if (SugarBody.inBody(event.target) && !isResizer(event.target as SugarElement)) {
path: modules/snooker/src/main/ts/ephox/snooker/resize/BarManager.ts
user: login: ztomaszyk
body: There is no parent element as the resizers are added inside the editor body.
created_at: 2025-04-11T14:51:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -36,8 +36,7 @@ const getInlineResizeWire = (editor: Editor, isResizable: (elm: SugarElement<Ele
     return ResizeWire.scrollable(editorBody, container, body, isResizable);
   }
 
-  Insert.append(body, container);
-  return ResizeWire.body(editorBody, container, isResizable);
+  return ResizeWire.body(editorBody, isResizable);
path: modules/tinymce/src/models/dom/main/ts/table/core/TableWire.ts
user: login: ztomaszyk
body: Applied the same logic for everything - non-inline, inline and split mode 😃 
created_at: 2025-04-11T14:52:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -10,7 +10,7 @@ const col = (column: number, x: number, y: number, w: number, h: number): SugarE
     width: w + 'px'
   });
 
-  Attribute.setAll(bar, { 'data-column': column, 'role': 'presentation' });
+  Attribute.setAll(bar, { 'data-mce-bogus': 'all', 'data-column': column, 'role': 'presentation' });
path: modules/snooker/src/main/ts/ephox/snooker/resize/Bar.ts
user: login: spocke
body: Could we add a test for this so that we know that these bars doesn't get rendered as the `getContent()` or generate a undo level. Like fake hover the table, then call `editor.getContent()` and/or `undoManager.add()`
created_at: 2025-04-14T08:15:46Z
author_association: MEMBER
diff_hunk: @@ -124,16 +124,21 @@ export const BarManager = (wire: ResizeWire): BarManager => {
   const findClosestEditableTable = (target: SugarElement<Node>): Optional<SugarElement<HTMLTableElement>> =>
     SelectorFind.closest<HTMLTableElement>(target, 'table', isRoot).filter(ContentEditable.isEditable);
 
+  const isResizer = (target: SugarElement<Element>): boolean =>
+    Class.has(target, 'ephox-snooker-resizer-bar') || Class.has(target, 'ephox-dragster-blocker');
+
   /* mouseover on table: When the mouse moves within the CONTENT AREA (NOT THE TABLE), refresh the bars. */
-  const mouseover = DomEvent.bind(wire.view(), 'mouseover', (event) => {
+  const mouseover = DomEvent.bind<MouseEvent, Element>(wire.view(), 'mouseover', (event) => {
     findClosestEditableTable(event.target).fold(
       () => {
         /*
         * mouseout is not reliable within ContentEditable, so for all other mouseover events we clear bars.
         * This is fairly safe to do frequently; it's a single querySelectorAll() on the content and Arr.map on the result.
         * If we _really_ need to optimise it further, we can start caching the bar references in the wire somehow.
+        *
+        * Because the resizers were moved into the editor for inline mode, we need to check if the event target is not a resizer.
         */
-        if (SugarBody.inBody(event.target)) {
+        if (SugarBody.inBody(event.target) && !isResizer(event.target)) {
path: modules/snooker/src/main/ts/ephox/snooker/resize/BarManager.ts
user: login: spocke
body: Could we add a test for this so hover and try to click on them. On click make sure that the bars are still there or something.
created_at: 2025-04-14T08:19:57Z
author_association: MEMBER
diff_hunk: @@ -132,8 +135,10 @@ export const BarManager = (wire: ResizeWire): BarManager => {
         * mouseout is not reliable within ContentEditable, so for all other mouseover events we clear bars.
         * This is fairly safe to do frequently; it's a single querySelectorAll() on the content and Arr.map on the result.
         * If we _really_ need to optimise it further, we can start caching the bar references in the wire somehow.
+        *
+        * Because the resizers were moved into the editor for inline mode, we need to check if the event target is not a resizer.
         */
-        if (SugarBody.inBody(event.target)) {
+        if (SugarBody.inBody(event.target) && !isResizer(event.target as SugarElement)) {
path: modules/snooker/src/main/ts/ephox/snooker/resize/BarManager.ts
user: login: TheSpyder
body: Ah. I'm thinking of the old editor where there was.
created_at: 2025-04-14T11:33:37Z
author_association: MEMBER
diff_hunk: @@ -121,10 +121,6 @@ export const TableResizeHandler = (editor: Editor): TableResizeHandler => {
     tableResize.on((sz) => {
       sz.destroy();
     });
-
-    resizeWire.on((w) => {
-      TableWire.remove(editor, w);
-    });
path: modules/tinymce/src/models/dom/main/ts/table/api/TableResizeHandler.ts
user: login: TheSpyder
body: It's perhaps a bit hard to follow in a PR thread, but why isn't this needed anymore?
created_at: 2025-04-14T11:35:14Z
author_association: MEMBER
diff_hunk: @@ -12,6 +12,7 @@
 import { EditorEvent } from 'tinymce/core/api/util/EventDispatcher';
 
 import * as TableTestUtils from '../../module/table/TableTestUtils';
+import { tab } from 'tinymce/plugins/help/ui/KeyboardShortcutsTab';
path: modules/tinymce/src/models/dom/test/ts/browser/table/ResizeTableTest.ts
user: login: github-advanced-security[bot]
body: ## Unused variable, import, function or class

Unused import tab.

[Show more details](https://github.com/tinymce/tinymce/security/code-scanning/139)
created_at: 2025-04-14T11:40:45Z
author_association: NONE
diff_hunk: @@ -121,10 +121,6 @@ export const TableResizeHandler = (editor: Editor): TableResizeHandler => {
     tableResize.on((sz) => {
       sz.destroy();
     });
-
-    resizeWire.on((w) => {
-      TableWire.remove(editor, w);
-    });
path: modules/tinymce/src/models/dom/main/ts/table/api/TableResizeHandler.ts
user: login: ztomaszyk
body: Removing the wire was there to remove the parent element for inline mode (aka resizers container that was added next to the editor). 

```
// (removed from `TableWire.ts`)
const remove = (editor: Editor, wire: ResizeWire): void => {
  if (editor.inline) {
    Remove.remove(wire.parent());
  }
};
```

As I moved the resizers into the editors body and there is no container anymore, there is nothing we need to clean up here. 
created_at: 2025-04-14T11:48:12Z
author_association: CONTRIBUTOR

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-04-08T15:33:02Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

This pull request refactors the table resizing functionality across two modules. In the snooker module, parameters are simplified by replacing variable arguments with a direct function call and adding a new utility to check resizer targets. In the TinyMCE module, several functions related to inline resize wire handling have been removed or simplified, and test assertions have been updated accordingly. A new changelog entry documents a fix to ensure table resizers are visible when an inline editor sets a z-index.

## Changes

| File(s) | Change Summary |
|---------|----------------|
| `modules/snooker/src/demo/ts/ephox/snooker/demo/DetectDemo.ts` | Replaced variable parameters (`ltrs`, `rtls`) with the `isResizable` function in `ResizeWire.body` calls within `TableResize.create` for both ltr and rtl managers. |
| `modules/snooker/src/main/ts/ephox/snooker/api/ResizeWire.ts` | Removed the `chrome` parameter from the `body` function signature; updated internal logic to use `editable`; removed the export of `scrollable`. |
| `modules/snooker/src/main/ts/ephox/snooker/resize/BarManager.ts`, `modules/snooker/src/main/ts/ephox/snooker/resize/Bar.ts` | Introduced a new `isResizer` function in `BarManager` for checking resizer elements during mouseover events with explicit type parameters; added the attribute `'data-mce-bogus': 'all'` to bar elements in both `col` and `row` functions. |
| `modules/tinymce/src/models/dom/main/ts/table/core/TableWire.ts`, `modules/tinymce/src/models/dom/main/ts/table/api/TableResizeHandler.ts` | Removed functions handling inline resize wire (i.e. `createContainer`, `getInlineResizeWire`, `remove`), simplified the `get` function to directly return a `ResizeWire` based on the editor body, and eliminated an event listener on the resize wire. |
| `modules/tinymce/src/core/test/ts/browser/init/InitEditorNoThemeInlineTest.ts`, `modules/tinymce/src/models/dom/test/ts/browser/table/ResizeTableTest.ts` | Updated tests by removing the retrieval of a next sibling element and renaming assertions to directly verify the existence or absence of resize bars in the editor body. |
| `.changes/unreleased/tinymce-TINY-11981-2025-04-11.yaml` | Added a new changelog entry documenting a fix that makes table resizers visible when an inline editor uses a z-index property. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User
    participant BarManager
    participant isResizer

    User->>BarManager: Mouseover event on table area
    BarManager->>isResizer: Check if target is a resizer
    isResizer-->>BarManager: Return true/false
    alt Target is resizer
        BarManager->>BarManager: Skip bar destruction
    else Target is not a resizer
        BarManager->>BarManager: Proceed to destroy bars
    end
```

```mermaid
sequenceDiagram
    participant Editor
    participant TableWire
    participant ResizeWire

    Editor->>TableWire: Request ResizeWire (editor body, isResizable)
    TableWire->>ResizeWire: Create and return ResizeWire instance
    ResizeWire-->>Editor: Return instance
```

<!-- walkthrough_end -->

<!-- announcements_start -->

> [!TIP]
> <details>
> <summary>⚡💬 Agentic Chat (Pro Plan, General Availability)</summary>
> 
> - We're introducing multi-step agentic chat in review comments and issue comments, within and outside of PR's. This feature enhances review and issue discussions with the CodeRabbit agentic chat by enabling advanced interactions, including the ability to create pull requests directly from comments and add commits to existing pull requests.
> 
> </details>

<!-- announcements_end -->

---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between bdad8d2eb26ac771328464e87cf834edf40344f4 and 41886e968704e0a47362c7889550629c93928cee.

</details>

<details>
<summary>📒 Files selected for processing (9)</summary>

* `.changes/unreleased/tinymce-TINY-11981-2025-04-11.yaml` (1 hunks)
* `modules/snooker/src/demo/ts/ephox/snooker/demo/DetectDemo.ts` (1 hunks)
* `modules/snooker/src/main/ts/ephox/snooker/api/ResizeWire.ts` (1 hunks)
* `modules/snooker/src/main/ts/ephox/snooker/resize/Bar.ts` (2 hunks)
* `modules/snooker/src/main/ts/ephox/snooker/resize/BarManager.ts` (1 hunks)
* `modules/tinymce/src/core/test/ts/browser/init/InitEditorNoThemeInlineTest.ts` (2 hunks)
* `modules/tinymce/src/models/dom/main/ts/table/api/TableResizeHandler.ts` (0 hunks)
* `modules/tinymce/src/models/dom/main/ts/table/core/TableWire.ts` (1 hunks)
* `modules/tinymce/src/models/dom/test/ts/browser/table/ResizeTableTest.ts` (7 hunks)

</details>

<details>
<summary>💤 Files with no reviewable changes (1)</summary>

* modules/tinymce/src/models/dom/main/ts/table/api/TableResizeHandler.ts

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (4)</summary>

* modules/snooker/src/demo/ts/ephox/snooker/demo/DetectDemo.ts
* .changes/unreleased/tinymce-TINY-11981-2025-04-11.yaml
* modules/snooker/src/main/ts/ephox/snooker/resize/Bar.ts
* modules/tinymce/src/core/test/ts/browser/init/InitEditorNoThemeInlineTest.ts

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: Analyze (javascript)

</details>

<details>
<summary>🔇 Additional comments (15)</summary><blockquote>

<details>
<summary>modules/snooker/src/main/ts/ephox/snooker/resize/BarManager.ts (3)</summary>

`127-128`: **Utility function for checking resizer elements looks good.**

This new utility function `isResizer` checks if an element has either of the two resizer-related classes, which will be useful for preventing the destruction of resize bars when the mouse is over a resizer element.

You could consider using `Classes.hasAny(target, ['ephox-snooker-resizer-bar', 'ephox-dragster-blocker'])` for a slightly more concise implementation, but the current approach is fine for only two classes.

---

`131-131`: **Improved type safety for event binding.**

Good job explicitly typing the event as `MouseEvent` and the target as `Element` rather than using a type cast later in the code.

---

`139-144`: **Fix to maintain resizers when hovering over them in inline mode.**

This change addresses the core issue where resize bars were being destroyed when hovering over the resizers themselves in inline mode. Using the new `isResizer` utility function prevents this undesired behavior.

Have you considered adding tests that specifically check for this behavior? It would be good to ensure this fix doesn't regress in the future.

</details>
<details>
<summary>modules/snooker/src/main/ts/ephox/snooker/api/ResizeWire.ts (2)</summary>

`46-54`: **Simplified ResizeWire.body API by removing the chrome parameter.**

This is a good simplification that makes the API more streamlined for the inline mode use case. The removal of the `chrome` parameter and the use of `editable` for all wire-related elements (parent, dragContainer, etc.) makes the code cleaner.

The change to use `SugarLocation.absolute(editable)` for the origin is also appropriate as it ensures correct positioning relative to the editable area rather than using a fixed position.

---

`56-60`: **Removed 'scrollable' from exports.**

Removing `scrollable` from the exports is consistent with the simplification approach, as it's no longer needed with the new inline mode resizing implementation.

</details>
<details>
<summary>modules/tinymce/src/models/dom/main/ts/table/core/TableWire.ts (1)</summary>

`1-14`: **Significantly simplified TableWire implementation.**

The entire implementation has been streamlined to directly use the editor body for resizing functionality. This is a good simplification that:

1. Removes the need for separate container creation and management
2. Makes the code more maintainable with fewer edge cases
3. Ensures consistent behavior between inline and normal modes

This change aligns well with the modifications to the `ResizeWire.body` function in the previous file.

</details>
<details>
<summary>modules/tinymce/src/models/dom/test/ts/browser/table/ResizeTableTest.ts (9)</summary>

`1183-1186`: **Updated assertion function to check for resize bars in the editor body.**

This renamed function correctly reflects the new implementation where resize bars are placed directly in the editor body rather than in a separate container.

---

`1188-1191`: **Updated assertion function to check absence of resize bars.**

Similarly, this function has been properly updated to check for the absence of resize bars in the editor body.

---

`1202-1204`: **Corrected positioning calculation for resize bars.**

The calculation now correctly positions the resize bars relative to the editor body's top rather than the document, which is particularly important for inline mode.

---

`1210-1214`: **Added helper function for clicking on resizers.**

This utility function is useful for testing interactions with the resize bars and ensures consistent selection of the resizer elements.

---

`1216-1221`: **Testing the location of table resize bars is important.**

This test context properly validates that resize bars are correctly positioned inside the editor body for the iframe mode.

---

`1229-1240`: **Test case for hovering over non-table elements.**

This is an important test case that verifies the fix for the issue mentioned in the PR description. It ensures that resize bars are removed when hovering over non-table elements but preserved when hovering over resizer elements themselves.

---

`1284-1295`: **Same test for inline mode as for iframe mode.**

Good job maintaining consistency by implementing the same test for inline mode as for iframe mode. This ensures that the behavior is consistent across different editor modes.

---

`1377-1385`: **Test for inline mode with scrollable container.**

This test is particularly valuable as it checks a more complex scenario with scrollable containers, ensuring that the resize bars still behave correctly even when the editor is inside a scrollable area.

---

`1415-1429`: **Test to ensure resize bars don't appear in content.**

This is an important test that verifies that the resize bars are purely UI elements and don't contaminate the actual content when it's retrieved via getContent(). This ensures that saving or exporting the content will be clean without resize UI artifacts.

</details>

</blockquote></details>

</details>
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=tinymce/tinymce&utm_content=10275):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai plan` to trigger planning for file edits and PR creation.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: ztomaszyk
created_at: 2025-04-08T15:33:12Z
body: * **#10275** <a href="https://app.graphite.dev/github/pr/tinymce/tinymce/10275?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a> 👈 <a href="https://app.graphite.dev/github/pr/tinymce/tinymce/10275?utm_source=stack-comment-view-in-graphite" target="_blank">(View in Graphite)</a>
* `main`


<h2></h2>

This stack of pull requests is managed by <a href="https://graphite.dev?utm-source=stack-comment"><b>Graphite</b></a>. Learn more about <a href="https://stacking.dev/?utm_source=stack-comment">stacking</a>.
<!-- Current dependencies on/for this PR: -->
author_association: CONTRIBUTOR