Skip to content

Commit

Permalink
Enable selecting image when the only element in the range is an Image (
Browse files Browse the repository at this point in the history
…#2554)

* init

* Address comment

* Reuse isReverted from Range Selection

* Fix build

* Fix build

* Unselect image when Up or Down, or it remains selected

* remove unneeded changes and improve name of tests

* Update

---------

Co-authored-by: Julia Roldi <87443959+juliaroldi@users.noreply.github.com>
  • Loading branch information
BryanValverdeU and juliaroldi authored Apr 5, 2024
1 parent 35c3a33 commit b7d50b4
Show file tree
Hide file tree
Showing 7 changed files with 435 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ export function addRangeToSelection(doc: Document, range: Range, isReverted: boo
const selection = doc.defaultView?.getSelection();

if (selection) {
const currentRange = selection.rangeCount > 0 && selection.getRangeAt(0);
if (
currentRange &&
currentRange.startContainer == range.startContainer &&
currentRange.endContainer == range.endContainer &&
currentRange.startOffset == range.startOffset &&
currentRange.endOffset == range.endOffset
) {
return;
}
selection.removeAllRanges();

if (!isReverted) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ import type {

const DOM_SELECTION_CSS_KEY = '_DOMSelection';
const HIDE_CURSOR_CSS_KEY = '_DOMSelectionHideCursor';
const HIDE_SELECTION_CSS_KEY = '_DOMSelectionHideSelection';
const IMAGE_ID = 'image';
const TABLE_ID = 'table';
const DEFAULT_SELECTION_BORDER_COLOR = '#DB626C';
const TABLE_CSS_RULE = 'background-color:#C6C6C6!important;';
const CARET_CSS_RULE = 'caret-color: transparent';
const TRANSPARENT_SELECTION_CSS_RULE = 'background-color: transparent !important';
const SELECTION_SELECTOR = '*::selection';

/**
* @internal
Expand All @@ -31,6 +34,7 @@ export const setDOMSelection: SetDOMSelection = (core, selection, skipSelectionC
core.selection.skipReselectOnFocus = true;
core.api.setEditorStyle(core, DOM_SELECTION_CSS_KEY, null /*cssRule*/);
core.api.setEditorStyle(core, HIDE_CURSOR_CSS_KEY, null /*cssRule*/);
core.api.setEditorStyle(core, HIDE_SELECTION_CSS_KEY, null /*cssRule*/);

try {
switch (selection?.type) {
Expand All @@ -46,9 +50,14 @@ export const setDOMSelection: SetDOMSelection = (core, selection, skipSelectionC
}!important;`,
[`#${ensureUniqueId(image, IMAGE_ID)}`]
);
core.api.setEditorStyle(core, HIDE_CURSOR_CSS_KEY, CARET_CSS_RULE);
core.api.setEditorStyle(
core,
HIDE_SELECTION_CSS_KEY,
TRANSPARENT_SELECTION_CSS_RULE,
[SELECTION_SELECTOR]
);

setRangeSelection(doc, image);
setRangeSelection(doc, image, false /* collapse */);
break;
case 'table':
const { table, firstColumn, firstRow, lastColumn, lastRow } = selection;
Expand Down Expand Up @@ -105,7 +114,11 @@ export const setDOMSelection: SetDOMSelection = (core, selection, skipSelectionC
const nodeToSelect = firstCell.cell?.firstElementChild || firstCell.cell;

if (nodeToSelect) {
setRangeSelection(doc, (nodeToSelect as HTMLElement) || undefined);
setRangeSelection(
doc,
(nodeToSelect as HTMLElement) || undefined,
true /* collapse */
);
}

break;
Expand Down Expand Up @@ -197,13 +210,24 @@ function handleTableSelected(
return selectors;
}

function setRangeSelection(doc: Document, element: HTMLElement | undefined) {
function setRangeSelection(doc: Document, element: HTMLElement | undefined, collapse: boolean) {
if (element && doc.contains(element)) {
const range = doc.createRange();
let isReverted: boolean | undefined = undefined;

range.selectNode(element);
range.collapse();
if (collapse) {
range.collapse();
} else {
const selection = doc.defaultView?.getSelection();
const range = selection && selection.rangeCount > 0 && selection.getRangeAt(0);
if (selection && range) {
isReverted =
selection.focusNode != range.endContainer ||
selection.focusOffset != range.endOffset;
}
}

addRangeToSelection(doc, range);
addRangeToSelection(doc, range, isReverted);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { findCoordinate } from './findCoordinate';
import { findTableCellElement } from '../../coreApi/setDOMSelection/findTableCellElement';
import { isSingleImageInSelection } from './isSingleImageInSelection';
import { normalizePos } from './normalizePos';
import {
isCharacterValue,
Expand All @@ -21,6 +22,7 @@ import type {
ParsedTable,
TableSelectionInfo,
TableCellCoordinate,
RangeSelection,
} from 'roosterjs-content-model-types';

const MouseLeftButton = 0;
Expand Down Expand Up @@ -126,8 +128,7 @@ class SelectionPlugin implements PluginWithState<SelectionPluginState> {
this.getContainedTargetImage(rawEvent, selection)) &&
image.isContentEditable
) {
this.selectImage(image);

this.selectImageWithRange(image, rawEvent);
return;
} else if (selection?.type == 'image' && selection.image !== rawEvent.target) {
this.selectBeforeOrAfterElement(editor, selection.image);
Expand Down Expand Up @@ -232,6 +233,25 @@ class SelectionPlugin implements PluginWithState<SelectionPluginState> {
}
};

private selectImageWithRange(image: HTMLImageElement, event: Event) {
const range = image.ownerDocument.createRange();
range.selectNode(image);

const domSelection = this.editor?.getDOMSelection();
if (domSelection?.type == 'image' && image == domSelection.image) {
event.preventDefault();
} else {
this.setDOMSelection(
{
type: 'range',
isReverted: false,
range,
},
null
);
}
}

private onMouseUp(event: MouseUpEvent) {
let image: HTMLImageElement | null;

Expand All @@ -243,7 +263,7 @@ class SelectionPlugin implements PluginWithState<SelectionPluginState> {
MouseRightButton /* it's not possible to drag using right click */ ||
event.isClicking)
) {
this.selectImage(image);
this.selectImageWithRange(image, event.rawEvent);
}

this.detachMouseEvent();
Expand Down Expand Up @@ -442,16 +462,6 @@ class SelectionPlugin implements PluginWithState<SelectionPluginState> {
}
}

private selectImage(image: HTMLImageElement) {
this.setDOMSelection(
{
type: 'image',
image: image,
},
null /*tableSelection*/
);
}

private selectBeforeOrAfterElement(editor: IEditor, element: HTMLElement, after?: boolean) {
const doc = editor.getDocument();
const parent = element.parentNode;
Expand Down Expand Up @@ -525,22 +535,27 @@ class SelectionPlugin implements PluginWithState<SelectionPluginState> {

//If am image selection changed to a wider range due a keyboard event, we should update the selection
const selection = this.editor.getDocument().getSelection();
if (
newSelection?.type == 'image' &&
selection &&
selection.containsNode(newSelection.image, false /*partiallyContained*/)
) {
this.editor.setDOMSelection({
type: 'range',
range: selection.getRangeAt(0),
isReverted: false,
});

if (newSelection?.type == 'image' && selection) {
if (selection && !isSingleImageInSelection(selection)) {
const range = selection.getRangeAt(0);
this.editor.setDOMSelection({
type: 'range',
range,
isReverted:
selection.focusNode != range.endContainer ||
selection.focusOffset != range.endOffset,
});
}
}

// Safari has problem to handle onBlur event. When blur, we cannot get the original selection from editor.
// So we always save a selection whenever editor has focus. Then after blur, we can still use this cached selection.
if (newSelection?.type == 'range' && this.isSafari) {
this.state.selection = newSelection;
if (newSelection?.type == 'range') {
if (this.isSafari) {
this.state.selection = newSelection;
}
this.trySelectSingleImage(newSelection);
}
}
};
Expand Down Expand Up @@ -611,6 +626,21 @@ class SelectionPlugin implements PluginWithState<SelectionPluginState> {
this.state.mouseDisposer = undefined;
}
}

private trySelectSingleImage(selection: RangeSelection) {
if (!selection.range.collapsed) {
const image = isSingleImageInSelection(selection.range);
if (image) {
this.setDOMSelection(
{
type: 'image',
image: image,
},
null /*tableSelection*/
);
}
}
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { isElementOfType, isNodeOfType } from 'roosterjs-content-model-dom';

/**
* @internal
*/
export function isSingleImageInSelection(selection: Selection | Range): HTMLImageElement | null {
const { startNode, endNode, startOffset, endOffset } = getProps(selection);

const max = Math.max(startOffset, endOffset);
const min = Math.min(startOffset, endOffset);

if (startNode && endNode && startNode == endNode && max - min == 1) {
const node = startNode?.childNodes.item(min);
if (isNodeOfType(node, 'ELEMENT_NODE') && isElementOfType(node, 'img')) {
return node;
}
}
return null;
}
function getProps(
selection: Selection | Range
): { startNode: Node | null; endNode: Node | null; startOffset: number; endOffset: number } {
if (isSelection(selection)) {
return {
startNode: selection.anchorNode,
endNode: selection.focusNode,
startOffset: selection.anchorOffset,
endOffset: selection.focusOffset,
};
} else {
return {
startNode: selection.startContainer,
endNode: selection.endContainer,
startOffset: selection.startOffset,
endOffset: selection.endOffset,
};
}
}

function isSelection(selection: Selection | Range): selection is Selection {
return !!(selection as Selection).getRangeAt;
}
Loading

0 comments on commit b7d50b4

Please sign in to comment.