Skip to content

Commit 7f7ba78

Browse files
authored
Merge pull request #3176 from microsoft/u/nguyenvi/bump-version-101025
Bump version to 9.39.0
2 parents 1c57815 + 0b83563 commit 7f7ba78

File tree

6 files changed

+487
-101
lines changed

6 files changed

+487
-101
lines changed

packages/roosterjs-content-model-plugins/lib/autoFormat/AutoFormatPlugin.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ChangeSource } from 'roosterjs-content-model-dom';
22
import { checkAndInsertHorizontalLine } from './horizontalLine/checkAndInsertHorizontalLine';
33
import { createLink } from './link/createLink';
44
import { formatTextSegmentBeforeSelectionMarker, promoteLink } from 'roosterjs-content-model-api';
5+
import { getListTypeStyle } from './list/getListTypeStyle';
56
import { keyboardListTrigger } from './list/keyboardListTrigger';
67
import { transformFraction } from './numbers/transformFraction';
78
import { transformHyphen } from './hyphen/transformHyphen';
@@ -100,6 +101,42 @@ export class AutoFormatPlugin implements EditorPlugin {
100101
this.editor = null;
101102
}
102103

104+
private shouldHandleInputEventExclusively(editor: IEditor, event: EditorInputEvent) {
105+
const rawEvent = event.rawEvent;
106+
const selection = editor.getDOMSelection();
107+
let shouldHandle = false;
108+
if (
109+
rawEvent.inputType === 'insertText' &&
110+
selection &&
111+
selection.type === 'range' &&
112+
selection.range.collapsed &&
113+
rawEvent.data == ' '
114+
) {
115+
formatTextSegmentBeforeSelectionMarker(editor, (model, previousSegment, paragraph) => {
116+
const { autoLink, autoTel, autoMailto, autoBullet, autoNumbering } = this.options;
117+
const list = getListTypeStyle(model, autoBullet, autoNumbering);
118+
const link = promoteLink(previousSegment, paragraph, {
119+
autoLink,
120+
autoTel,
121+
autoMailto,
122+
});
123+
shouldHandle = !!link || !!list;
124+
return false;
125+
});
126+
}
127+
return shouldHandle;
128+
}
129+
130+
willHandleEventExclusively(event: PluginEvent) {
131+
if (this.editor) {
132+
switch (event.eventType) {
133+
case 'input':
134+
return this.shouldHandleInputEventExclusively(this.editor, event);
135+
}
136+
}
137+
return false;
138+
}
139+
103140
/**
104141
* Core method for a plugin. Once an event happens in editor, editor will call this
105142
* method of each plugin to handle the event as long as the event is not handled

packages/roosterjs-content-model-plugins/lib/tableEdit/editors/TableEditor.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,8 @@ export class TableEditor {
256256
true /*isHorizontal*/,
257257
this.onStartCellResize,
258258
this.onFinishEditing,
259-
this.anchorContainer
259+
this.anchorContainer,
260+
this.onTableEditorCreated
260261
);
261262
this.verticalResizer = createCellResizer(
262263
this.editor,
@@ -266,7 +267,8 @@ export class TableEditor {
266267
false /*isHorizontal*/,
267268
this.onStartCellResize,
268269
this.onFinishEditing,
269-
this.anchorContainer
270+
this.anchorContainer,
271+
this.onTableEditorCreated
270272
);
271273
}
272274
}

packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/CellResizer.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
} from 'roosterjs-content-model-dom';
1313
import type { DragAndDropHandler } from '../../../pluginUtils/DragAndDrop/DragAndDropHandler';
1414
import type { IEditor, ReadonlyContentModelTable } from 'roosterjs-content-model-types';
15+
import type { OnTableEditorCreatedCallback } from '../../OnTableEditorCreatedCallback';
1516

1617
const CELL_RESIZER_WIDTH = 4;
1718
/**
@@ -34,7 +35,8 @@ export function createCellResizer(
3435
isHorizontal: boolean,
3536
onStart: () => void,
3637
onEnd: () => false,
37-
anchorContainer?: HTMLElement
38+
anchorContainer?: HTMLElement,
39+
onTableEditorCreated?: OnTableEditorCreatedCallback
3840
): TableEditFeature | null {
3941
const document = td.ownerDocument;
4042
const createElementData = {
@@ -66,18 +68,42 @@ export function createCellResizer(
6668
onDragEnd: onEnd,
6769
};
6870

69-
const featureHandler = new DragAndDropHelper<CellResizerContext, CellResizerInitValue>(
71+
const featureHandler = new CellResizer(
7072
div,
7173
context,
7274
setPosition,
7375
handler,
7476
zoomScale,
75-
editor.getEnvironment().isMobileOrTablet
77+
editor.getEnvironment().isMobileOrTablet,
78+
onTableEditorCreated
7679
);
7780

7881
return { node: td, div, featureHandler };
7982
}
8083

84+
class CellResizer extends DragAndDropHelper<CellResizerContext, CellResizerInitValue> {
85+
private disposer: undefined | (() => void);
86+
87+
constructor(
88+
trigger: HTMLElement,
89+
context: CellResizerContext,
90+
onSubmit: (context: CellResizerContext, trigger: HTMLElement) => void,
91+
handler: DragAndDropHandler<CellResizerContext, CellResizerInitValue>,
92+
zoomScale: number,
93+
forceMobile?: boolean,
94+
onTableEditorCreated?: OnTableEditorCreatedCallback
95+
) {
96+
super(trigger, context, onSubmit, handler, zoomScale, forceMobile);
97+
this.disposer = onTableEditorCreated?.('CellResizer', trigger);
98+
}
99+
100+
dispose(): void {
101+
this.disposer?.();
102+
this.disposer = undefined;
103+
super.dispose();
104+
}
105+
}
106+
81107
/**
82108
* @internal
83109
* Exported for testing

packages/roosterjs-content-model-plugins/lib/touch/TouchPlugin.ts

Lines changed: 56 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,10 @@
1-
import type {
2-
EditorPlugin,
3-
IEditor,
4-
PluginEvent,
5-
ReadonlyContentModelDocument,
6-
} from 'roosterjs-content-model-types';
1+
import type { EditorPlugin, IEditor, PluginEvent } from 'roosterjs-content-model-types';
72
import { getNodePositionFromEvent } from '../utils/getNodePositionFromEvent';
8-
import {
9-
getSelectedSegmentsAndParagraphs,
10-
mutateBlock,
11-
createSelectionMarker,
12-
} from 'roosterjs-content-model-dom';
13-
import { adjustWordSelection } from 'roosterjs-content-model-api';
143

154
const MAX_TOUCH_MOVE_DISTANCE = 6; // the max number of offsets for the touch selection to move
165
const POINTER_DETECTION_DELAY = 150; // Delay time to wait for selection to be updated and also detect if pointerup is a tap or part of double tap
176
const PUNCTUATION_MATCHING_REGEX = /[.,;:!]/;
187
const SPACE_MATCHING_REGEX = /\s/;
19-
const CARET_CSS_RULE = 'caret-color: transparent';
20-
const HIDE_CURSOR_CSS_KEY = '_DOMSelectionHideCursor';
218

229
/**
2310
* Touch plugin to manage touch behaviors
@@ -72,7 +59,7 @@ export class TouchPlugin implements EditorPlugin {
7259
case 'pointerDown':
7360
this.isDblClicked = false;
7461
this.isTouchPenPointerEvent = true;
75-
this.editor.setEditorStyle(HIDE_CURSOR_CSS_KEY, CARET_CSS_RULE);
62+
event.originalEvent.preventDefault();
7663

7764
const targetWindow = this.editor.getDocument()?.defaultView || window;
7865
if (this.timer) {
@@ -84,16 +71,66 @@ export class TouchPlugin implements EditorPlugin {
8471

8572
if (this.editor) {
8673
if (!this.isDblClicked) {
87-
this.editor.formatContentModel(model =>
88-
this.repositionTouchSelection(model)
74+
this.editor.focus();
75+
const caretPosition = getNodePositionFromEvent(
76+
this.editor,
77+
event.rawEvent.x,
78+
event.rawEvent.y
8979
);
80+
81+
const newRange = this.editor.getDocument().createRange();
82+
if (caretPosition) {
83+
const { node, offset } = caretPosition;
84+
85+
// Place cursor at same position of browser handler by default
86+
newRange.setStart(node, offset);
87+
newRange.setEnd(node, offset);
88+
89+
const nodeTextContent = node.textContent || '';
90+
const charAtSelection = nodeTextContent[offset];
91+
if (
92+
node.nodeType === Node.TEXT_NODE &&
93+
charAtSelection &&
94+
!SPACE_MATCHING_REGEX.test(charAtSelection) &&
95+
!PUNCTUATION_MATCHING_REGEX.test(charAtSelection)
96+
) {
97+
const { wordStart, wordEnd } = findWordBoundaries(
98+
nodeTextContent,
99+
offset
100+
);
101+
102+
// Move cursor to the calculated offset
103+
const leftCursorWordLength = offset - wordStart;
104+
const rightCursorWordLength = wordEnd - offset;
105+
let movingOffset: number =
106+
leftCursorWordLength >= rightCursorWordLength
107+
? rightCursorWordLength
108+
: -leftCursorWordLength;
109+
movingOffset =
110+
Math.abs(movingOffset) > MAX_TOUCH_MOVE_DISTANCE
111+
? 0
112+
: movingOffset;
113+
const newOffsetPosition = offset + movingOffset;
114+
if (
115+
movingOffset !== 0 &&
116+
nodeTextContent.length >= newOffsetPosition
117+
) {
118+
newRange.setStart(node, newOffsetPosition);
119+
newRange.setEnd(node, newOffsetPosition);
120+
}
121+
}
122+
}
123+
this.editor.setDOMSelection({
124+
type: 'range',
125+
range: newRange,
126+
isReverted: false,
127+
});
128+
90129
// reset values
91130
this.isTouchPenPointerEvent = false;
92131
}
93-
this.editor.setEditorStyle(HIDE_CURSOR_CSS_KEY, null);
94132
}
95133
}, POINTER_DETECTION_DELAY);
96-
97134
break;
98135
case 'doubleClick':
99136
if (this.isTouchPenPointerEvent) {
@@ -177,82 +214,6 @@ export class TouchPlugin implements EditorPlugin {
177214
break;
178215
}
179216
}
180-
181-
repositionTouchSelection = (model: ReadonlyContentModelDocument) => {
182-
const segmentAndParagraphs = getSelectedSegmentsAndParagraphs(
183-
model,
184-
false /*includingFormatHolder*/,
185-
true /*includingEntity*/,
186-
true /*mutate*/
187-
);
188-
189-
const isCollapsedSelection =
190-
segmentAndParagraphs.length >= 1 &&
191-
segmentAndParagraphs.every(x => x[0].segmentType == 'SelectionMarker');
192-
193-
// 1. adjust selection to a word if selection is collapsed
194-
if (isCollapsedSelection) {
195-
const para = segmentAndParagraphs[0][1];
196-
const segments = adjustWordSelection(model, segmentAndParagraphs[0][0]);
197-
198-
if (
199-
segments.length > 2 &&
200-
segments.some(x => x.segmentType == 'Text' && !x.isSelected) &&
201-
para
202-
) {
203-
const selectionMarkerIndexInWord = segments.findIndex(
204-
segment => segment.segmentType == 'SelectionMarker'
205-
);
206-
const selectionMarkerIndexInPara = para.segments.findIndex(
207-
segment => segment.segmentType == 'SelectionMarker'
208-
);
209-
const leftSelectionSegmentsInWord = segments[selectionMarkerIndexInWord - 1];
210-
const rightSelectionSegmentsInWord = segments[selectionMarkerIndexInWord + 1];
211-
const leftCursorWordLength =
212-
leftSelectionSegmentsInWord.segmentType == 'Text'
213-
? leftSelectionSegmentsInWord.text.length
214-
: 0;
215-
const rightCursorWordLength =
216-
rightSelectionSegmentsInWord.segmentType == 'Text'
217-
? rightSelectionSegmentsInWord.text.length
218-
: 0;
219-
220-
// Move the cursor to the closest edge of the word if the distance is within threshold = 6
221-
if (rightCursorWordLength > leftCursorWordLength) {
222-
if (leftCursorWordLength < MAX_TOUCH_MOVE_DISTANCE) {
223-
// Move cursor to beginning of word
224-
// Remove old marker
225-
mutateBlock(para).segments.splice(selectionMarkerIndexInPara, 1);
226-
227-
// Add new marker
228-
const indexSegmentBeforeMarker = para.segments.findIndex(
229-
segment => segment === leftSelectionSegmentsInWord
230-
);
231-
const marker = createSelectionMarker(
232-
segments[selectionMarkerIndexInPara]?.format || para.format
233-
);
234-
mutateBlock(para).segments.splice(indexSegmentBeforeMarker, 0, marker);
235-
}
236-
} else {
237-
// Move cursor to end of word
238-
if (rightCursorWordLength < MAX_TOUCH_MOVE_DISTANCE) {
239-
// Add new marker
240-
const indexSegmentAfterMarker = para.segments.findIndex(
241-
segment => segment === rightSelectionSegmentsInWord
242-
);
243-
const marker = createSelectionMarker(
244-
segments[selectionMarkerIndexInPara]?.format || para.format
245-
);
246-
mutateBlock(para).segments.splice(indexSegmentAfterMarker + 1, 0, marker);
247-
248-
// Remove old marker
249-
mutateBlock(para).segments.splice(selectionMarkerIndexInPara, 1);
250-
}
251-
}
252-
}
253-
}
254-
return true;
255-
};
256217
}
257218

258219
/**

0 commit comments

Comments
 (0)