diff --git a/demo/scripts/controls/BuildInPluginState.ts b/demo/scripts/controls/BuildInPluginState.ts index 4aa4f850a83..47ac1c80f26 100644 --- a/demo/scripts/controls/BuildInPluginState.ts +++ b/demo/scripts/controls/BuildInPluginState.ts @@ -34,6 +34,7 @@ export default interface BuildInPluginState { experimentalFeatures: ExperimentalFeatures[]; forcePreserveRatio: boolean; isRtl: boolean; + tableFeaturesContainerSelector: string; } export interface BuildInPluginProps extends BuildInPluginState, SidePaneElementProps {} diff --git a/demo/scripts/controls/MainPaneBase.tsx b/demo/scripts/controls/MainPaneBase.tsx index ff2fb6b97f7..ccd28c983a7 100644 --- a/demo/scripts/controls/MainPaneBase.tsx +++ b/demo/scripts/controls/MainPaneBase.tsx @@ -176,7 +176,7 @@ export default abstract class MainPaneBase extends React.Component<{}, MainPaneB this.updateContentPlugin.forceUpdate(); return ( -
+
{this.state.editorCreator && ( void + ) => void, + private anchorContainerSelector?: string ) {} /** @@ -131,11 +135,16 @@ export default class TableResize implements EditorPlugin { } if (!this.tableEditor && table && this.editor && table.rows.length > 0) { + const container = this.anchorContainerSelector + ? this.editor.getDocument().querySelector(this.anchorContainerSelector) + : undefined; + this.tableEditor = new TableEditor( this.editor, table, this.invalidateTableRects, this.onShowHelperElement, + safeInstanceOf(container, 'HTMLElement') ? container : undefined, e?.currentTarget ); } diff --git a/packages/roosterjs-editor-plugins/lib/plugins/TableResize/editors/TableEditor.ts b/packages/roosterjs-editor-plugins/lib/plugins/TableResize/editors/TableEditor.ts index e2650028815..d5146c2981e 100644 --- a/packages/roosterjs-editor-plugins/lib/plugins/TableResize/editors/TableEditor.ts +++ b/packages/roosterjs-editor-plugins/lib/plugins/TableResize/editors/TableEditor.ts @@ -81,6 +81,7 @@ export default class TableEditor { elementData: CreateElementData, helperType: 'CellResizer' | 'TableInserter' | 'TableResizer' | 'TableSelector' ) => void, + private anchorContainer?: HTMLElement, private contentDiv?: EventTarget | null ) { this.isRTL = getComputedStyle(table, 'direction') == 'rtl'; @@ -211,23 +212,24 @@ export default class TableEditor { if (!this.tableSelector) { this.tableSelector = createTableSelector( this.table, - this.editor.getZoomScale(), this.editor, this.onSelect, this.getOnMouseOut, this.onShowHelperElement, - this.contentDiv + this.contentDiv, + this.anchorContainer ); } if (!this.tableResizer) { this.tableResizer = createTableResizer( this.table, - this.editor.getZoomScale(), - this.isRTL, + this.editor, this.onStartTableResize, this.onFinishEditing, - this.onShowHelperElement + this.onShowHelperElement, + this.contentDiv, + this.anchorContainer ); } } diff --git a/packages/roosterjs-editor-plugins/lib/plugins/TableResize/editors/TableResizer.ts b/packages/roosterjs-editor-plugins/lib/plugins/TableResize/editors/TableResizer.ts index f383ffcb5b4..3214ee7f643 100644 --- a/packages/roosterjs-editor-plugins/lib/plugins/TableResize/editors/TableResizer.ts +++ b/packages/roosterjs-editor-plugins/lib/plugins/TableResize/editors/TableResizer.ts @@ -1,7 +1,13 @@ import DragAndDropHelper from '../../../pluginUtils/DragAndDropHelper'; import TableEditFeature from './TableEditorFeature'; -import { createElement, normalizeRect, VTable } from 'roosterjs-editor-dom'; -import { CreateElementData } from 'roosterjs-editor-types'; +import { CreateElementData, IEditor, Rect } from 'roosterjs-editor-types'; +import { + createElement, + getComputedStyle, + normalizeRect, + safeInstanceOf, + VTable, +} from 'roosterjs-editor-dom'; const TABLE_RESIZER_LENGTH = 12; const MIN_CELL_WIDTH = 30; @@ -12,16 +18,25 @@ const MIN_CELL_HEIGHT = 20; */ export default function createTableResizer( table: HTMLTableElement, - zoomScale: number, - isRTL: boolean, + editor: IEditor, onStart: () => void, onDragEnd: () => false, onShowHelperElement?: ( elementData: CreateElementData, helperType: 'CellResizer' | 'TableInserter' | 'TableResizer' | 'TableSelector' - ) => void + ) => void, + contentDiv?: EventTarget | null, + anchorContainer?: HTMLElement ): TableEditFeature | null { + const rect = normalizeRect(table.getBoundingClientRect()); + + if (!isTableBottomVisible(editor, rect, contentDiv)) { + return null; + } + const document = table.ownerDocument; + const isRTL = getComputedStyle(table, 'direction') == 'rtl'; + const zoomScale = editor.getZoomScale(); const createElementData = { tag: 'div', style: `position: fixed; cursor: ${ @@ -35,7 +50,8 @@ export default function createTableResizer( div.style.width = `${TABLE_RESIZER_LENGTH}px`; div.style.height = `${TABLE_RESIZER_LENGTH}px`; - document.body.appendChild(div); + + (anchorContainer || document.body).appendChild(div); const context: DragAndDropContext = { isRTL, @@ -44,12 +60,12 @@ export default function createTableResizer( onStart, }; - setResizeDivPosition(context, div); + setDivPosition(context, div); const featureHandler = new DragAndDropHelper( div, context, - setResizeDivPosition, + setDivPosition, { onDragStart, onDragging, @@ -137,7 +153,7 @@ function onDragging( } } -function setResizeDivPosition(context: DragAndDropContext, trigger: HTMLElement) { +function setDivPosition(context: DragAndDropContext, trigger: HTMLElement) { const { table, isRTL } = context; const rect = normalizeRect(table.getBoundingClientRect()); @@ -148,3 +164,22 @@ function setResizeDivPosition(context: DragAndDropContext, trigger: HTMLElement) : `${rect.right}px`; } } + +function isTableBottomVisible( + editor: IEditor, + rect: Rect | null, + contentDiv?: EventTarget | null +): boolean { + const visibleViewport = editor.getVisibleViewport(); + if (contentDiv && safeInstanceOf(contentDiv, 'HTMLElement') && visibleViewport && rect) { + const containerRect = normalizeRect(contentDiv.getBoundingClientRect()); + + return ( + !!containerRect && + containerRect.bottom >= rect.bottom && + visibleViewport.bottom >= rect.bottom + ); + } + + return true; +} diff --git a/packages/roosterjs-editor-plugins/lib/plugins/TableResize/editors/TableSelector.ts b/packages/roosterjs-editor-plugins/lib/plugins/TableResize/editors/TableSelector.ts index 35b507828a5..b1536f81847 100644 --- a/packages/roosterjs-editor-plugins/lib/plugins/TableResize/editors/TableSelector.ts +++ b/packages/roosterjs-editor-plugins/lib/plugins/TableResize/editors/TableSelector.ts @@ -1,8 +1,13 @@ import DragAndDropHandler from '../../../pluginUtils/DragAndDropHandler'; import DragAndDropHelper from '../../../pluginUtils/DragAndDropHelper'; import TableEditorFeature from './TableEditorFeature'; -import { createElement, normalizeRect, safeInstanceOf } from 'roosterjs-editor-dom'; import { CreateElementData, IEditor, Rect } from 'roosterjs-editor-types'; +import { + createElement, + normalizeRect, + safeInstanceOf, + getComputedStyle, +} from 'roosterjs-editor-dom'; const TABLE_SELECTOR_LENGTH = 12; const TABLE_SELECTOR_ID = '_Table_Selector'; @@ -12,7 +17,6 @@ const TABLE_SELECTOR_ID = '_Table_Selector'; */ export default function createTableSelector( table: HTMLTableElement, - zoomScale: number, editor: IEditor, onFinishDragging: (table: HTMLTableElement) => void, getOnMouseOut: (feature: HTMLElement) => (ev: MouseEvent) => void, @@ -20,7 +24,8 @@ export default function createTableSelector( elementData: CreateElementData, helperType: 'CellResizer' | 'TableInserter' | 'TableResizer' | 'TableSelector' ) => void, - contentDiv?: EventTarget | null + contentDiv?: EventTarget | null, + anchorContainer?: HTMLElement ): TableEditorFeature | null { const rect = normalizeRect(table.getBoundingClientRect()); @@ -28,6 +33,7 @@ export default function createTableSelector( return null; } + const zoomScale = editor.getZoomScale(); const document = table.ownerDocument; const createElementData = { tag: 'div', @@ -41,15 +47,17 @@ export default function createTableSelector( div.id = TABLE_SELECTOR_ID; div.style.width = `${TABLE_SELECTOR_LENGTH}px`; div.style.height = `${TABLE_SELECTOR_LENGTH}px`; - document.body.appendChild(div); + + (anchorContainer || document.body).appendChild(div); const context: TableSelectorContext = { table, zoomScale, rect, + isRTL: getComputedStyle(table, 'direction') == 'rtl', }; - setSelectorDivPosition(context, div); + setDivPosition(context, div); const onDragEnd = (context: TableSelectorContext, event: MouseEvent): false => { if (event.target == div) { @@ -61,11 +69,11 @@ export default function createTableSelector( const featureHandler = new TableSelectorFeature( div, context, - setSelectorDivPosition, + setDivPosition, { onDragEnd, }, - zoomScale, + context.zoomScale, getOnMouseOut ); @@ -76,6 +84,7 @@ interface TableSelectorContext { table: HTMLTableElement; zoomScale: number; rect: Rect | null; + isRTL: boolean; } interface TableSelectorInitValue { @@ -88,11 +97,16 @@ class TableSelectorFeature extends DragAndDropHelper void, + onSubmit: ( + context: TableSelectorContext, + trigger: HTMLElement, + container?: HTMLElement + ) => void, handler: DragAndDropHandler, zoomScale: number, getOnMouseOut: (feature: HTMLElement) => (ev: MouseEvent) => void, - forceMobile?: boolean + forceMobile?: boolean | undefined, + container?: HTMLElement ) { super(div, context, onSubmit, handler, zoomScale, forceMobile); this.onMouseOut = getOnMouseOut(div); @@ -108,7 +122,7 @@ class TableSelectorFeature extends DragAndDropHelper { //Act const result = createTableSelector( target as HTMLTableElement, - 1, editor, () => {}, () => () => {}, () => {}, - node + node ); //Assert