Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix table selector and resizer insertion #1984

Merged
merged 20 commits into from
Aug 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions demo/scripts/controls/BuildInPluginState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export default interface BuildInPluginState {
experimentalFeatures: ExperimentalFeatures[];
forcePreserveRatio: boolean;
isRtl: boolean;
tableFeaturesContainerSelector: string;
}

export interface BuildInPluginProps extends BuildInPluginState, SidePaneElementProps {}
2 changes: 1 addition & 1 deletion demo/scripts/controls/MainPaneBase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ export default abstract class MainPaneBase extends React.Component<{}, MainPaneB
this.updateContentPlugin.forceUpdate();

return (
<div className={styles.editorContainer}>
<div className={styles.editorContainer} id="EditorContainer">
<div style={editorStyles}>
{this.state.editorCreator && (
<Rooster
Expand Down
4 changes: 3 additions & 1 deletion demo/scripts/controls/getToggleablePlugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ export default function getToggleablePlugins(initState: BuildInPluginState) {
imageEdit,
cutPasteListChain: pluginList.cutPasteListChain ? new CutPasteListChain() : null,
tableCellSelection: pluginList.tableCellSelection ? new TableCellSelection() : null,
tableResize: pluginList.tableResize ? new TableResize() : null,
tableResize: pluginList.tableResize
? new TableResize(undefined, initState.tableFeaturesContainerSelector)
: null,
customReplace: pluginList.customReplace ? new CustomReplacePlugin() : null,
autoFormat: pluginList.autoFormat ? new AutoFormat() : null,
listEditMenu:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const initialState: BuildInPluginState = {
ExperimentalFeatures.ContentModelPaste,
],
isRtl: false,
tableFeaturesContainerSelector: '#' + 'EditorContainer',
};

export default class ContentModelEditorOptionsPlugin extends SidePanePluginImpl<
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ export default class ContentModelOptionsPane extends React.Component<
experimentalFeatures: this.state.experimentalFeatures,
forcePreserveRatio: this.state.forcePreserveRatio,
isRtl: this.state.isRtl,
tableFeaturesContainerSelector: this.state.tableFeaturesContainerSelector,
};

if (callback) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const initialState: BuildInPluginState = {
ExperimentalFeatures.InlineEntityReadOnlyDelimiters,
],
isRtl: false,
tableFeaturesContainerSelector: '#' + 'EditorContainer',
};

export default class EditorOptionsPlugin extends SidePanePluginImpl<
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ export default class OptionsPane extends React.Component<BuildInPluginProps, Bui
experimentalFeatures: this.state.experimentalFeatures,
forcePreserveRatio: this.state.forcePreserveRatio,
isRtl: this.state.isRtl,
tableFeaturesContainerSelector: this.state.tableFeaturesContainerSelector,
};

if (callback) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,16 @@ export default class TableResize implements EditorPlugin {
* @param onShowHelperElement An optional callback to allow customize helper element of table resizing.
* To customize the helper element, add this callback and change the attributes of elementData then it
* will be picked up by TableResize code
* @param anchorContainerSelector An optional selector string to specify the container to host the plugin.
* The container must not be affected by transform: scale(), otherwise the position calculation will be wrong.
* If not specified, the plugin will be inserted in document.body
*/
constructor(
private onShowHelperElement?: (
elementData: CreateElementData,
helperType: 'CellResizer' | 'TableInserter' | 'TableResizer' | 'TableSelector'
) => void
) => void,
private anchorContainerSelector?: string
) {}

/**
Expand Down Expand Up @@ -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
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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
Andres-CT98 marked this conversation as resolved.
Show resolved Hide resolved
);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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: ${
Expand All @@ -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,
Expand All @@ -44,12 +60,12 @@ export default function createTableResizer(
onStart,
};

setResizeDivPosition(context, div);
setDivPosition(context, div);

const featureHandler = new DragAndDropHelper<DragAndDropContext, DragAndDropInitValue>(
div,
context,
setResizeDivPosition,
setDivPosition,
{
onDragStart,
onDragging,
Expand Down Expand Up @@ -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());

Expand All @@ -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;
}
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -12,22 +17,23 @@ 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,
onShowHelperElement?: (
elementData: CreateElementData,
helperType: 'CellResizer' | 'TableInserter' | 'TableResizer' | 'TableSelector'
) => void,
contentDiv?: EventTarget | null
contentDiv?: EventTarget | null,
anchorContainer?: HTMLElement
): TableEditorFeature | null {
const rect = normalizeRect(table.getBoundingClientRect());

if (!isTableTopVisible(editor, rect, contentDiv)) {
return null;
}

const zoomScale = editor.getZoomScale();
const document = table.ownerDocument;
const createElementData = {
tag: 'div',
Expand All @@ -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) {
Expand All @@ -61,11 +69,11 @@ export default function createTableSelector(
const featureHandler = new TableSelectorFeature(
div,
context,
setSelectorDivPosition,
setDivPosition,
{
onDragEnd,
},
zoomScale,
context.zoomScale,
getOnMouseOut
);

Expand All @@ -76,6 +84,7 @@ interface TableSelectorContext {
table: HTMLTableElement;
zoomScale: number;
rect: Rect | null;
isRTL: boolean;
}

interface TableSelectorInitValue {
Expand All @@ -88,11 +97,16 @@ class TableSelectorFeature extends DragAndDropHelper<TableSelectorContext, Table
constructor(
private div: HTMLElement,
context: TableSelectorContext,
onSubmit: (context: TableSelectorContext, trigger: HTMLElement) => void,
onSubmit: (
context: TableSelectorContext,
trigger: HTMLElement,
container?: HTMLElement
) => void,
handler: DragAndDropHandler<TableSelectorContext, TableSelectorInitValue>,
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);
Expand All @@ -108,7 +122,7 @@ class TableSelectorFeature extends DragAndDropHelper<TableSelectorContext, Table
}
}

function setSelectorDivPosition(context: TableSelectorContext, trigger: HTMLElement) {
function setDivPosition(context: TableSelectorContext, trigger: HTMLElement) {
const { rect } = context;
if (rect) {
trigger.style.top = `${rect.top - TABLE_SELECTOR_LENGTH}px`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,11 @@ describe('Table Selector Tests', () => {
//Act
const result = createTableSelector(
target as HTMLTableElement,
1,
editor,
() => {},
() => () => {},
() => {},
<EventTarget>node
<HTMLElement>node
);

//Assert
Expand Down