suneditor 3.2.0 → 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/suneditor.min.css +1 -1
- package/dist/suneditor.min.js +1 -1
- package/package.json +1 -1
- package/src/assets/suneditor.css +4 -3
- package/src/core/event/eventOrchestrator.js +26 -7
- package/src/core/event/handlers/handler_ww_input.js +75 -0
- package/src/core/event/handlers/handler_ww_key.js +10 -18
- package/src/core/event/ports.js +4 -8
- package/src/core/event/reducers/keydown.reducer.js +18 -3
- package/src/core/section/constructor.js +0 -13
- package/types/core/event/eventOrchestrator.d.ts +7 -2
- package/types/core/event/ports.d.ts +4 -2
- package/types/core/event/reducers/keydown.reducer.d.ts +12 -3
package/package.json
CHANGED
package/src/assets/suneditor.css
CHANGED
|
@@ -1044,9 +1044,10 @@
|
|
|
1044
1044
|
color: var(--se-block-handle-color);
|
|
1045
1045
|
z-index: 10;
|
|
1046
1046
|
transition:
|
|
1047
|
-
top 0.
|
|
1048
|
-
left 0.
|
|
1049
|
-
right 0.
|
|
1047
|
+
top 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94),
|
|
1048
|
+
left 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94),
|
|
1049
|
+
right 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
|
1050
|
+
will-change: top, left, right;
|
|
1050
1051
|
}
|
|
1051
1052
|
|
|
1052
1053
|
.sun-editor .se-block-handle.se-no-transition {
|
|
@@ -112,9 +112,6 @@ class EventOrchestrator extends KernelInjector {
|
|
|
112
112
|
this.__eventDoc = null;
|
|
113
113
|
/** @type {string} */
|
|
114
114
|
this.__secopy = null;
|
|
115
|
-
|
|
116
|
-
/** @type {HTMLInputElement} */
|
|
117
|
-
this.__focusTemp = this.#contextProvider.carrierWrapper.querySelector('.__se__focus__temp__');
|
|
118
115
|
}
|
|
119
116
|
|
|
120
117
|
/**
|
|
@@ -206,6 +203,32 @@ class EventOrchestrator extends KernelInjector {
|
|
|
206
203
|
return this.defaultLineManager.execute(formatName);
|
|
207
204
|
}
|
|
208
205
|
|
|
206
|
+
/**
|
|
207
|
+
* @internal
|
|
208
|
+
* @description Normalize the Enter range before the reducer reads it: reset to a text node, and when the
|
|
209
|
+
* caret sits inside a zero-width text node adjacent to a `<br>`, move it onto the `<br>`.
|
|
210
|
+
* @returns {?(HTMLElement|Text)} The updated selection node, or `null` when the caret is not on a line (no normalization ran).
|
|
211
|
+
*/
|
|
212
|
+
_normalizeEnterRange() {
|
|
213
|
+
if (!this.$.format.isLine(this.$.selection.getRange()?.startContainer)) return null;
|
|
214
|
+
|
|
215
|
+
this.$.selection.resetRangeToTextNode();
|
|
216
|
+
|
|
217
|
+
const r = this.$.selection.getRange();
|
|
218
|
+
if (
|
|
219
|
+
r.startContainer === r.endContainer &&
|
|
220
|
+
r.startOffset !== r.endOffset &&
|
|
221
|
+
r.startContainer.nodeType === 3 &&
|
|
222
|
+
dom.check.isZeroWidth(r.startContainer)
|
|
223
|
+
) {
|
|
224
|
+
const br = r.startContainer.nextSibling;
|
|
225
|
+
if (br && dom.check.isBreak(br)) this.$.selection.setRange(br, 0, br, 0);
|
|
226
|
+
else this.$.selection.setRange(r.endContainer, r.endOffset, r.endContainer, r.endOffset);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return this.$.selection.getNode();
|
|
230
|
+
}
|
|
231
|
+
|
|
209
232
|
/**
|
|
210
233
|
* @internal
|
|
211
234
|
* @description Handles data transfer actions for `paste` and `drop` events.
|
|
@@ -579,9 +602,6 @@ class EventOrchestrator extends KernelInjector {
|
|
|
579
602
|
this.#eventManager.addEvent(parent, 'scroll', OnScrollAbs, false);
|
|
580
603
|
}
|
|
581
604
|
|
|
582
|
-
/** focus temp (mobile) */
|
|
583
|
-
this.#eventManager.addEvent(this.__focusTemp, 'focus', (e) => e.preventDefault(), false);
|
|
584
|
-
|
|
585
605
|
/** document event */
|
|
586
606
|
if (this.__eventDoc !== fc.get('_wd')) {
|
|
587
607
|
this.__eventDoc = fc.get('_wd');
|
|
@@ -658,7 +678,6 @@ class EventOrchestrator extends KernelInjector {
|
|
|
658
678
|
this.__inputPlugin = null;
|
|
659
679
|
this.__inputBlurEvent = null;
|
|
660
680
|
this.__inputKeyEvent = null;
|
|
661
|
-
this.__focusTemp = null;
|
|
662
681
|
this.__eventDoc = null;
|
|
663
682
|
this.__secopy = null;
|
|
664
683
|
this._lineBreakComp = null;
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import { dom, keyCodeMap } from '../../../helper';
|
|
2
|
+
import { actionExecutor } from '../executor';
|
|
3
|
+
import { makePorts } from '../ports';
|
|
4
|
+
import { reduceEnterDown } from '../rules/keydown.rule.enter';
|
|
5
|
+
import { ENTER_FROM_BEFOREINPUT } from '../reducers/keydown.reducer';
|
|
6
|
+
|
|
7
|
+
// The Enter rule/effects never touch the retain-style node cache (a backspace concern) — a local
|
|
8
|
+
// placeholder is enough to satisfy `makePorts`.
|
|
9
|
+
const _enterStyleNodes = { value: [] };
|
|
2
10
|
|
|
3
11
|
/**
|
|
4
12
|
* @typedef {import('../eventOrchestrator').default} EventManagerThis_handler_ww_input
|
|
@@ -16,6 +24,14 @@ export async function OnBeforeInput_wysiwyg(fc, e) {
|
|
|
16
24
|
return false;
|
|
17
25
|
}
|
|
18
26
|
|
|
27
|
+
// Enter is dispatched here — not on keydown — so the IME has finished committing before the DOM
|
|
28
|
+
// mutates (iOS/mobile marked-text stability). `insertParagraph` = Enter, `insertLineBreak` = Shift+Enter.
|
|
29
|
+
// ctrl/alt+Enter are shortcuts and never produce these inputTypes, so they stay on the keydown path.
|
|
30
|
+
if (ENTER_FROM_BEFOREINPUT && (e.inputType === 'insertParagraph' || e.inputType === 'insertLineBreak')) {
|
|
31
|
+
await dispatchEnter.call(this, fc, e);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
19
35
|
const data = (e.data === null ? '' : e.data === undefined ? ' ' : e.data) || '';
|
|
20
36
|
if (!keyCodeMap.isComposing(e)) {
|
|
21
37
|
if (!this.$.char.test(data, false)) {
|
|
@@ -35,6 +51,65 @@ export async function OnBeforeInput_wysiwyg(fc, e) {
|
|
|
35
51
|
await this._callPluginEventAsync('onBeforeInput', { frameContext: fc, event: e, data });
|
|
36
52
|
}
|
|
37
53
|
|
|
54
|
+
/**
|
|
55
|
+
* @this {EventManagerThis_handler_ww_input}
|
|
56
|
+
* @description Runs SunEditor's Enter logic from the `beforeinput` event (post-IME-commit) by reusing the
|
|
57
|
+
* exact keydown Enter rule + effects — only the dispatch site moves off keydown. See ENTER_FROM_BEFOREINPUT.
|
|
58
|
+
* The guards mirror `OnKeyDown_wysiwyg` (selectMenu, input-element, open-dropdown) so a `beforeinput` Enter
|
|
59
|
+
* is dropped in the same situations a keydown Enter is.
|
|
60
|
+
* @param {SunEditor.FrameContext} fc - Frame context object
|
|
61
|
+
* @param {InputEvent} e - The `beforeinput` event (`insertParagraph` | `insertLineBreak`)
|
|
62
|
+
*/
|
|
63
|
+
async function dispatchEnter(fc, e) {
|
|
64
|
+
// Skip while an IME composition is still active
|
|
65
|
+
if (e.isComposing || this.isComposing) return;
|
|
66
|
+
if (this.$.ui.selectMenuOn) return;
|
|
67
|
+
|
|
68
|
+
let selectionNode = this.$.selection.getNode();
|
|
69
|
+
if (dom.check.isInputElement(selectionNode)) return;
|
|
70
|
+
if (this.$.menu.currentDropdownName) return;
|
|
71
|
+
|
|
72
|
+
if (dom.check.isWysiwygFrame(selectionNode)) {
|
|
73
|
+
this._setDefaultLine(this.$.options.get('defaultLine'));
|
|
74
|
+
selectionNode = this.$.selection.getNode();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const normalized = this._normalizeEnterRange();
|
|
78
|
+
if (normalized) selectionNode = normalized;
|
|
79
|
+
|
|
80
|
+
const range = this.$.selection.getRange();
|
|
81
|
+
const formatEl = /** @type {HTMLElement} */ (this.$.format.getLine(selectionNode, null) || selectionNode);
|
|
82
|
+
const shift = e.inputType === 'insertLineBreak';
|
|
83
|
+
|
|
84
|
+
/** @type {import('../reducers/keydown.reducer').KeydownReducerCtx} */
|
|
85
|
+
const ctx = {
|
|
86
|
+
e,
|
|
87
|
+
fc,
|
|
88
|
+
store: this.$.store,
|
|
89
|
+
options: this.$.options,
|
|
90
|
+
frameOptions: this.$.frameOptions,
|
|
91
|
+
range,
|
|
92
|
+
selectionNode,
|
|
93
|
+
formatEl,
|
|
94
|
+
keyCode: 'Enter',
|
|
95
|
+
ctrl: false,
|
|
96
|
+
alt: false,
|
|
97
|
+
shift,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const ports = makePorts(this, { _styleNodes: _enterStyleNodes });
|
|
101
|
+
const actions = [];
|
|
102
|
+
reduceEnterDown(actions, ports, ctx);
|
|
103
|
+
|
|
104
|
+
// `beforeinput.preventDefault()` MUST run synchronously (before the first `await` below), or the browser
|
|
105
|
+
// commits its native insertParagraph/insertLineBreak on top of ours (duplicate line / cloned container).
|
|
106
|
+
if (actions.some((a) => a.t === 'event.prevent' || a.t === 'event.prevent.stop')) {
|
|
107
|
+
e.preventDefault();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
await actionExecutor(actions, { ports, ctx });
|
|
111
|
+
}
|
|
112
|
+
|
|
38
113
|
/**
|
|
39
114
|
* @this {EventManagerThis_handler_ww_input}
|
|
40
115
|
* @param {SunEditor.FrameContext} fc - Frame context object
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { dom, env, unicode, keyCodeMap } from '../../../helper';
|
|
2
2
|
import { actionExecutor } from '../executor';
|
|
3
3
|
import { makePorts } from '../ports';
|
|
4
|
-
import { reduceKeydown } from '../reducers/keydown.reducer';
|
|
4
|
+
import { reduceKeydown, ENTER_FROM_BEFOREINPUT } from '../reducers/keydown.reducer';
|
|
5
5
|
|
|
6
6
|
const { _w } = env;
|
|
7
7
|
const FRONT_ZEROWIDTH = new RegExp(unicode.zeroWidthSpace + '+', '');
|
|
@@ -47,23 +47,15 @@ export async function OnKeyDown_wysiwyg(fc, e) {
|
|
|
47
47
|
if (!this.$.store.mode.isSubBalloonAlways) this._hideToolbar_sub();
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
/**
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
dom.check.isZeroWidth(r.startContainer)
|
|
60
|
-
) {
|
|
61
|
-
const br = r.startContainer.nextSibling;
|
|
62
|
-
if (br && dom.check.isBreak(br)) this.$.selection.setRange(br, 0, br, 0);
|
|
63
|
-
else this.$.selection.setRange(r.endContainer, r.endOffset, r.endContainer, r.endOffset);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
selectionNode = this.$.selection.getNode();
|
|
50
|
+
/**
|
|
51
|
+
* default key action — normalize the Enter range before the reducer reads it.
|
|
52
|
+
* Skipped when Enter is handled from `beforeinput` (ENTER_FROM_BEFOREINPUT): mutating the DOM here
|
|
53
|
+
* on keydown re-traps the iOS/mobile IME marked-text — `dispatchEnter` runs this same normalization
|
|
54
|
+
* later, after the IME has committed.
|
|
55
|
+
*/
|
|
56
|
+
if (!ENTER_FROM_BEFOREINPUT && keyCodeMap.isEnter(keyCode)) {
|
|
57
|
+
const normalized = this._normalizeEnterRange();
|
|
58
|
+
if (normalized) selectionNode = normalized;
|
|
67
59
|
}
|
|
68
60
|
|
|
69
61
|
const range = this.$.selection.getRange();
|
package/src/core/event/ports.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { isMobile } from '../../helper/env';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* @typedef {import('./eventOrchestrator').default} EventManagerInstanceType
|
|
5
3
|
*/
|
|
@@ -187,15 +185,13 @@ export function makePorts(inst, { _styleNodes }) {
|
|
|
187
185
|
});
|
|
188
186
|
},
|
|
189
187
|
/**
|
|
190
|
-
* @description Prevents the default behavior of the `Enter` key
|
|
191
|
-
*
|
|
188
|
+
* @description Prevents the default behavior of the `Enter` key.
|
|
189
|
+
* Enter now runs from `beforeinput` (post-IME-commit), so the former mobile focus-shuffle
|
|
190
|
+
* (temp-focus → refocus, to force-end a virtual-keyboard IME session) is unnecessary.
|
|
191
|
+
* @param {Event} e The keyboard/input event
|
|
192
192
|
*/
|
|
193
193
|
enterPrevent(e) {
|
|
194
194
|
e.preventDefault();
|
|
195
|
-
if (!isMobile) return;
|
|
196
|
-
|
|
197
|
-
inst.__focusTemp.focus({ preventScroll: true });
|
|
198
|
-
frameContext.get('wysiwyg').focus({ preventScroll: true });
|
|
199
195
|
},
|
|
200
196
|
};
|
|
201
197
|
}
|
|
@@ -9,13 +9,23 @@ import { A } from '../actions';
|
|
|
9
9
|
|
|
10
10
|
const { isOSX_IOS } = env;
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* @description Enter is processed from the `beforeinput` event (post-IME-commit) instead of `keydown`,
|
|
14
|
+
* to avoid trapping iOS/mobile IME marked-text when `keydown` mutates the DOM (see `handler_ww_input.js`).
|
|
15
|
+
* Flip to `false` to instantly restore the legacy synchronous `keydown` Enter path — no other file needs
|
|
16
|
+
* touching for rollback (the `keydown` Enter gate, the `handler_ww_key` normalization guard, and the
|
|
17
|
+
* `beforeinput` dispatch all key off this single flag).
|
|
18
|
+
* @type {boolean}
|
|
19
|
+
*/
|
|
20
|
+
export const ENTER_FROM_BEFOREINPUT = true;
|
|
21
|
+
|
|
12
22
|
/**
|
|
13
23
|
* @typedef {import('../ports').EventReducerPorts} EventPorts
|
|
14
24
|
*/
|
|
15
25
|
|
|
16
26
|
/**
|
|
17
27
|
* @typedef {Object} KeydownReducerCtx - Keydown Reducer Context object
|
|
18
|
-
* @property {KeyboardEvent} ctx.e - The keyboard event
|
|
28
|
+
* @property {KeyboardEvent|InputEvent} ctx.e - The keyboard event (or the `beforeinput` InputEvent when Enter is dispatched from `beforeinput`)
|
|
19
29
|
* @property {SunEditor.FrameContext} ctx.fc - Frame context object
|
|
20
30
|
* @property {SunEditor.Store} ctx.store - Editor store object
|
|
21
31
|
* @property {SunEditor.Options} ctx.options - Options object
|
|
@@ -62,8 +72,13 @@ export async function reduceKeydown(ports, ctx) {
|
|
|
62
72
|
break;
|
|
63
73
|
}
|
|
64
74
|
case 'Enter' /** enter key */: {
|
|
65
|
-
|
|
66
|
-
|
|
75
|
+
// Enter is handled in `beforeinput` (IME stability) — see ENTER_FROM_BEFOREINPUT.
|
|
76
|
+
// Gate only (not an early-return) so the post-switch `documentTypeRefreshHeader`
|
|
77
|
+
// still runs for a selectRange + Enter on keydown.
|
|
78
|
+
if (!ENTER_FROM_BEFOREINPUT) {
|
|
79
|
+
if (reduceEnterDown(actions, ports, ctx) === false) {
|
|
80
|
+
return actions;
|
|
81
|
+
}
|
|
67
82
|
}
|
|
68
83
|
break;
|
|
69
84
|
}
|
|
@@ -76,19 +76,6 @@ function Constructor(editorTargets, options) {
|
|
|
76
76
|
// menuTray
|
|
77
77
|
const menuTray = dom.utils.createElement('DIV', { class: 'se-menu-tray', popover: 'manual' });
|
|
78
78
|
editor_carrier_wrapper.appendChild(menuTray);
|
|
79
|
-
// focus temp element
|
|
80
|
-
const focusTemp = /** @type {HTMLInputElement} */ (
|
|
81
|
-
dom.utils.createElement('INPUT', {
|
|
82
|
-
type: 'text',
|
|
83
|
-
id: editorFormFieldPrefix + '-focus-temp',
|
|
84
|
-
class: '__se__focus__temp__',
|
|
85
|
-
autocomplete: 'off',
|
|
86
|
-
'aria-hidden': 'true',
|
|
87
|
-
style: 'position: fixed !important; top: -10000px !important; left: -10000px !important; display: block !important; width: 0 !important; height: 0 !important; margin: 0 !important; padding: 0 !important;',
|
|
88
|
-
})
|
|
89
|
-
);
|
|
90
|
-
focusTemp.tabIndex = 0;
|
|
91
|
-
editor_carrier_wrapper.appendChild(focusTemp);
|
|
92
79
|
|
|
93
80
|
// modal
|
|
94
81
|
const modal = dom.utils.createElement('DIV', {
|
|
@@ -51,8 +51,6 @@ declare class EventOrchestrator extends KernelInjector {
|
|
|
51
51
|
__eventDoc: Document;
|
|
52
52
|
/** @type {string} */
|
|
53
53
|
__secopy: string;
|
|
54
|
-
/** @type {HTMLInputElement} */
|
|
55
|
-
__focusTemp: HTMLInputElement;
|
|
56
54
|
/**
|
|
57
55
|
* @description Activates the corresponding button with the tags information of the current cursor position,
|
|
58
56
|
* - such as `bold`, `underline`, etc., and executes the `active` method of the plugins.
|
|
@@ -86,6 +84,13 @@ declare class EventOrchestrator extends KernelInjector {
|
|
|
86
84
|
* @param {?string} formatName Format tag name (default: `P`)
|
|
87
85
|
*/
|
|
88
86
|
_setDefaultLine(formatName: string | null): void;
|
|
87
|
+
/**
|
|
88
|
+
* @internal
|
|
89
|
+
* @description Normalize the Enter range before the reducer reads it: reset to a text node, and when the
|
|
90
|
+
* caret sits inside a zero-width text node adjacent to a `<br>`, move it onto the `<br>`.
|
|
91
|
+
* @returns {?(HTMLElement|Text)} The updated selection node, or `null` when the caret is not on a line (no normalization ran).
|
|
92
|
+
*/
|
|
93
|
+
_normalizeEnterRange(): (HTMLElement | Text) | null;
|
|
89
94
|
/**
|
|
90
95
|
* @internal
|
|
91
96
|
* @description Handles data transfer actions for `paste` and `drop` events.
|
|
@@ -173,8 +173,10 @@ export function makePorts(
|
|
|
173
173
|
*/
|
|
174
174
|
enterScrollTo(range: Range): void;
|
|
175
175
|
/**
|
|
176
|
-
* @description Prevents the default behavior of the `Enter` key
|
|
177
|
-
*
|
|
176
|
+
* @description Prevents the default behavior of the `Enter` key.
|
|
177
|
+
* Enter now runs from `beforeinput` (post-IME-commit), so the former mobile focus-shuffle
|
|
178
|
+
* (temp-focus → refocus, to force-end a virtual-keyboard IME session) is unnecessary.
|
|
179
|
+
* @param {Event} e The keyboard/input event
|
|
178
180
|
*/
|
|
179
181
|
enterPrevent(e: Event): void;
|
|
180
182
|
};
|
|
@@ -4,7 +4,7 @@ import type {} from '../../../typedef';
|
|
|
4
4
|
*/
|
|
5
5
|
/**
|
|
6
6
|
* @typedef {Object} KeydownReducerCtx - Keydown Reducer Context object
|
|
7
|
-
* @property {KeyboardEvent} ctx.e - The keyboard event
|
|
7
|
+
* @property {KeyboardEvent|InputEvent} ctx.e - The keyboard event (or the `beforeinput` InputEvent when Enter is dispatched from `beforeinput`)
|
|
8
8
|
* @property {SunEditor.FrameContext} ctx.fc - Frame context object
|
|
9
9
|
* @property {SunEditor.Store} ctx.store - Editor store object
|
|
10
10
|
* @property {SunEditor.Options} ctx.options - Options object
|
|
@@ -27,15 +27,24 @@ import type {} from '../../../typedef';
|
|
|
27
27
|
* @returns {Promise<EventActions>} Action list
|
|
28
28
|
*/
|
|
29
29
|
export function reduceKeydown(ports: EventPorts, ctx: KeydownReducerCtx): Promise<EventActions>;
|
|
30
|
+
/**
|
|
31
|
+
* @description Enter is processed from the `beforeinput` event (post-IME-commit) instead of `keydown`,
|
|
32
|
+
* to avoid trapping iOS/mobile IME marked-text when `keydown` mutates the DOM (see `handler_ww_input.js`).
|
|
33
|
+
* Flip to `false` to instantly restore the legacy synchronous `keydown` Enter path — no other file needs
|
|
34
|
+
* touching for rollback (the `keydown` Enter gate, the `handler_ww_key` normalization guard, and the
|
|
35
|
+
* `beforeinput` dispatch all key off this single flag).
|
|
36
|
+
* @type {boolean}
|
|
37
|
+
*/
|
|
38
|
+
export const ENTER_FROM_BEFOREINPUT: boolean;
|
|
30
39
|
export type EventPorts = import('../ports').EventReducerPorts;
|
|
31
40
|
/**
|
|
32
41
|
* - Keydown Reducer Context object
|
|
33
42
|
*/
|
|
34
43
|
export type KeydownReducerCtx = {
|
|
35
44
|
/**
|
|
36
|
-
* - The keyboard event
|
|
45
|
+
* - The keyboard event (or the `beforeinput` InputEvent when Enter is dispatched from `beforeinput`)
|
|
37
46
|
*/
|
|
38
|
-
e: KeyboardEvent;
|
|
47
|
+
e: KeyboardEvent | InputEvent;
|
|
39
48
|
/**
|
|
40
49
|
* - Frame context object
|
|
41
50
|
*/
|