suneditor 3.2.0 → 3.2.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "suneditor",
3
- "version": "3.2.0",
3
+ "version": "3.2.2",
4
4
  "description": "Vanilla JavaScript based WYSIWYG web editor",
5
5
  "author": "Yi JiHong",
6
6
  "license": "MIT",
@@ -1044,9 +1044,10 @@
1044
1044
  color: var(--se-block-handle-color);
1045
1045
  z-index: 10;
1046
1046
  transition:
1047
- top 0.15s ease-out,
1048
- left 0.15s ease-out,
1049
- right 0.15s ease-out;
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 {
@@ -1347,6 +1348,7 @@
1347
1348
 
1348
1349
  /** --- popover UA override ---------------------------------------------------- */
1349
1350
  .sun-editor .se-controller[popover],
1351
+ .sun-editor .se-block-handle[popover],
1350
1352
  .sun-editor.sun-editor-carrier-wrapper .se-drag-cursor[popover] {
1351
1353
  inset: unset;
1352
1354
  margin: 0;
@@ -1357,6 +1359,7 @@
1357
1359
  .sun-editor .se-loading-box[popover],
1358
1360
  .sun-editor .se-toast[popover],
1359
1361
  .sun-editor .se-browser[popover],
1362
+ .sun-editor .se-block-handle[popover],
1360
1363
  .sun-editor .se-menu-tray[popover] {
1361
1364
  background: none;
1362
1365
  border: none;
@@ -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
- /** default key action — normalize the Enter range before the reducer reads it */
51
- if (keyCodeMap.isEnter(keyCode) && this.$.format.isLine(this.$.selection.getRange()?.startContainer)) {
52
- this.$.selection.resetRangeToTextNode();
53
-
54
- const r = this.$.selection.getRange();
55
- if (
56
- r.startContainer === r.endContainer &&
57
- r.startOffset !== r.endOffset &&
58
- r.startContainer.nodeType === 3 &&
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();
@@ -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 and refocuses the editor.
191
- * @param {Event} e The keyboard event
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
- if (reduceEnterDown(actions, ports, ctx) === false) {
66
- return actions;
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
  }
@@ -119,6 +119,30 @@ class BlockHandle {
119
119
  em.addEvent(this.#$.frameContext.get('eventWysiwyg'), 'keydown', this.#onWrapperKeyDown.bind(this), true);
120
120
  }
121
121
 
122
+ /**
123
+ * @description Show the handle.
124
+ */
125
+ #showHandle() {
126
+ this.#handle.style.display = 'flex';
127
+ try {
128
+ this.#handle.showPopover?.();
129
+ } catch {
130
+ // already open
131
+ }
132
+ }
133
+
134
+ /**
135
+ * @description Hide the handle and drop it out of the top layer.
136
+ */
137
+ #hideHandle() {
138
+ try {
139
+ this.#handle.hidePopover?.();
140
+ } catch {
141
+ // already hidden
142
+ }
143
+ this.#handle.style.display = 'none';
144
+ }
145
+
122
146
  /**
123
147
  * @description Position the block handle for the given mouse target. Uses rAF throttle.
124
148
  * Called from wysiwyg mousemove.
@@ -156,7 +180,7 @@ class BlockHandle {
156
180
  */
157
181
  hideNow() {
158
182
  this.#cancelHide();
159
- this.#handle.style.display = 'none';
183
+ this.#hideHandle();
160
184
  this.#actionMenu?.close();
161
185
  this.#setCurrentBlock(null);
162
186
  }
@@ -267,7 +291,7 @@ class BlockHandle {
267
291
  if (this.#hideTimer) return;
268
292
  this.#hideTimer = _w.setTimeout(() => {
269
293
  this.#hideTimer = null;
270
- this.#handle.style.display = 'none';
294
+ this.#hideHandle();
271
295
  this.#actionMenu?.close();
272
296
  this.#setCurrentBlock(null);
273
297
  }, 200);
@@ -323,11 +347,8 @@ class BlockHandle {
323
347
  */
324
348
  #onAreaMouseLeave(e) {
325
349
  if (this.#actionMenu?.isOpen) return;
326
-
327
- const related = /** @type {Node} */ (e?.relatedTarget);
328
- if (related && this.#handle?.contains(related)) return;
329
-
330
- this.#scheduleHide();
350
+ if (this.#stayAlive(/** @type {Node} */ (e?.relatedTarget))) return;
351
+ this.hideNow();
331
352
  }
332
353
 
333
354
  /**
@@ -335,11 +356,19 @@ class BlockHandle {
335
356
  */
336
357
  #onWrapperMouseLeave(e) {
337
358
  if (this.#actionMenu?.isOpen) return;
338
- const related = /** @type {Node} */ (e?.relatedTarget);
339
- if (related && this.#handle?.contains(related)) return;
340
- this.#cancelHide();
341
- this.#handle.style.display = 'none';
342
- this.#setCurrentBlock(null);
359
+ if (this.#stayAlive(/** @type {Node} */ (e?.relatedTarget))) return;
360
+ this.hideNow();
361
+ }
362
+
363
+ /**
364
+ * @description Whether a mouseleave's `relatedTarget` is still within the editor interaction zone
365
+ * @param {Node|null} related - `relatedTarget` of the mouseleave event
366
+ * @returns {boolean}
367
+ */
368
+ #stayAlive(related) {
369
+ if (!related) return false;
370
+ const wrapper = this.#area?.parentElement;
371
+ return !!(this.#handle?.contains(related) || wrapper?.contains(related));
343
372
  }
344
373
 
345
374
  /**
@@ -412,13 +441,13 @@ class BlockHandle {
412
441
  if (isIframe) {
413
442
  const iframeH = wysiwygFrameEl.clientHeight || 0;
414
443
  if (blockRect.bottom <= 0 || blockRect.top >= iframeH) {
415
- this.#handle.style.display = 'none';
444
+ this.#hideHandle();
416
445
  return;
417
446
  }
418
447
  } else {
419
448
  const wwFrameRect = wysiwygFrameEl.getBoundingClientRect();
420
449
  if (blockRect.bottom <= wwFrameRect.top || blockRect.top >= wwFrameRect.bottom) {
421
- this.#handle.style.display = 'none';
450
+ this.#hideHandle();
422
451
  return;
423
452
  }
424
453
  }
@@ -460,7 +489,7 @@ class BlockHandle {
460
489
  this.#handle.style.right = '';
461
490
  this.#handle.style.left = areaRect.left + scrollX + totalOffset + 'px';
462
491
  }
463
- this.#handle.style.display = 'flex';
492
+ this.#showHandle();
464
493
 
465
494
  if (wasHidden) {
466
495
  void this.#handle.offsetHeight;
@@ -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', {
@@ -312,7 +299,7 @@ function Constructor(editorTargets, options) {
312
299
  if (isBlockHandle) {
313
300
  blockHandleArea = dom.utils.createElement('DIV', { class: 'se-block-handle-area' });
314
301
 
315
- const blockHandleGroup = dom.utils.createElement('DIV', { class: 'se-block-handle' });
302
+ const blockHandleGroup = dom.utils.createElement('DIV', { class: 'se-block-handle', popover: 'manual' });
316
303
  const blockHandlePlus = dom.utils.createElement(
317
304
  'DIV',
318
305
  { class: 'se-block-handle-btn se-block-handle-plus' },
@@ -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 and refocuses the editor.
177
- * @param {Event} e The keyboard event
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
  */