@moda-editor/core 0.3.1 → 0.3.3

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.
Files changed (137) hide show
  1. package/dist/commands.d.ts.map +1 -1
  2. package/dist/commands.js +14 -1
  3. package/dist/commands.js.map +1 -1
  4. package/dist/editor/editor.d.ts.map +1 -1
  5. package/dist/editor/editor.js +3 -1
  6. package/dist/editor/editor.js.map +1 -1
  7. package/dist/features/code-block.d.ts.map +1 -1
  8. package/dist/features/code-block.js +10 -2
  9. package/dist/features/code-block.js.map +1 -1
  10. package/dist/features/code-edit.d.ts.map +1 -1
  11. package/dist/features/code-edit.js.map +1 -1
  12. package/dist/features/code-format.js.map +1 -1
  13. package/dist/features/hard-break.d.ts.map +1 -1
  14. package/dist/features/hard-break.js +2 -0
  15. package/dist/features/hard-break.js.map +1 -1
  16. package/dist/features/heading.d.ts.map +1 -1
  17. package/dist/features/heading.js +3 -1
  18. package/dist/features/heading.js.map +1 -1
  19. package/dist/features/horizontal-rule.d.ts.map +1 -1
  20. package/dist/features/horizontal-rule.js.map +1 -1
  21. package/dist/features/image.d.ts.map +1 -1
  22. package/dist/features/image.js.map +1 -1
  23. package/dist/features/link.d.ts.map +1 -1
  24. package/dist/features/link.js.map +1 -1
  25. package/dist/features/marks.d.ts.map +1 -1
  26. package/dist/features/marks.js +25 -3
  27. package/dist/features/marks.js.map +1 -1
  28. package/dist/features/search.d.ts.map +1 -1
  29. package/dist/features/search.js +1 -7
  30. package/dist/features/search.js.map +1 -1
  31. package/dist/features/table.d.ts.map +1 -1
  32. package/dist/features/table.js +30 -7
  33. package/dist/features/table.js.map +1 -1
  34. package/dist/fixtures/external-pastes.d.ts +33 -0
  35. package/dist/fixtures/external-pastes.d.ts.map +1 -0
  36. package/dist/fixtures/external-pastes.js +134 -0
  37. package/dist/fixtures/external-pastes.js.map +1 -0
  38. package/dist/history.d.ts.map +1 -1
  39. package/dist/history.js +57 -2
  40. package/dist/history.js.map +1 -1
  41. package/dist/index.d.ts +6 -5
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +3 -3
  44. package/dist/index.js.map +1 -1
  45. package/dist/model/node.d.ts.map +1 -1
  46. package/dist/model/node.js +1 -2
  47. package/dist/model/node.js.map +1 -1
  48. package/dist/model/schema.d.ts +7 -0
  49. package/dist/model/schema.d.ts.map +1 -1
  50. package/dist/model/schema.js.map +1 -1
  51. package/dist/model/traverse.d.ts +10 -3
  52. package/dist/model/traverse.d.ts.map +1 -1
  53. package/dist/model/traverse.js +13 -5
  54. package/dist/model/traverse.js.map +1 -1
  55. package/dist/parse/html.d.ts.map +1 -1
  56. package/dist/parse/html.js +33 -3
  57. package/dist/parse/html.js.map +1 -1
  58. package/dist/presets/markdown-parse.d.ts.map +1 -1
  59. package/dist/presets/markdown-parse.js +9 -0
  60. package/dist/presets/markdown-parse.js.map +1 -1
  61. package/dist/presets/markdown.d.ts.map +1 -1
  62. package/dist/presets/markdown.js +7 -6
  63. package/dist/presets/markdown.js.map +1 -1
  64. package/dist/presets/starter.d.ts.map +1 -1
  65. package/dist/presets/starter.js.map +1 -1
  66. package/dist/state/apply.d.ts.map +1 -1
  67. package/dist/state/apply.js +1 -3
  68. package/dist/state/apply.js.map +1 -1
  69. package/dist/state/input-rules.d.ts.map +1 -1
  70. package/dist/state/input-rules.js +4 -0
  71. package/dist/state/input-rules.js.map +1 -1
  72. package/dist/state/insert-ops.d.ts.map +1 -1
  73. package/dist/state/insert-ops.js +9 -2
  74. package/dist/state/insert-ops.js.map +1 -1
  75. package/dist/state/list-ops.d.ts.map +1 -1
  76. package/dist/state/list-ops.js.map +1 -1
  77. package/dist/state/navigate.d.ts.map +1 -1
  78. package/dist/state/navigate.js +14 -4
  79. package/dist/state/navigate.js.map +1 -1
  80. package/dist/state/resolve-insert.d.ts.map +1 -1
  81. package/dist/state/resolve-insert.js +12 -1
  82. package/dist/state/resolve-insert.js.map +1 -1
  83. package/dist/styles.css +27 -12
  84. package/dist/theme.d.ts.map +1 -1
  85. package/dist/theme.js.map +1 -1
  86. package/dist/view/decoration.d.ts.map +1 -1
  87. package/dist/view/decoration.js +60 -44
  88. package/dist/view/decoration.js.map +1 -1
  89. package/dist/view/highlight-registry.d.ts.map +1 -1
  90. package/dist/view/highlight-registry.js.map +1 -1
  91. package/dist/view/ime.d.ts +22 -1
  92. package/dist/view/ime.d.ts.map +1 -1
  93. package/dist/view/ime.js +124 -56
  94. package/dist/view/ime.js.map +1 -1
  95. package/dist/view/input-beforeinput.d.ts +25 -0
  96. package/dist/view/input-beforeinput.d.ts.map +1 -0
  97. package/dist/view/input-beforeinput.js +330 -0
  98. package/dist/view/input-beforeinput.js.map +1 -0
  99. package/dist/view/input-keys.d.ts +25 -0
  100. package/dist/view/input-keys.d.ts.map +1 -0
  101. package/dist/view/input-keys.js +69 -0
  102. package/dist/view/input-keys.js.map +1 -0
  103. package/dist/view/input-pointer.d.ts +24 -0
  104. package/dist/view/input-pointer.d.ts.map +1 -0
  105. package/dist/view/input-pointer.js +96 -0
  106. package/dist/view/input-pointer.js.map +1 -0
  107. package/dist/view/input-selection.d.ts +41 -0
  108. package/dist/view/input-selection.d.ts.map +1 -0
  109. package/dist/view/input-selection.js +219 -0
  110. package/dist/view/input-selection.js.map +1 -0
  111. package/dist/view/input.d.ts +39 -30
  112. package/dist/view/input.d.ts.map +1 -1
  113. package/dist/view/input.js +84 -679
  114. package/dist/view/input.js.map +1 -1
  115. package/dist/view/mount.d.ts +7 -0
  116. package/dist/view/mount.d.ts.map +1 -1
  117. package/dist/view/mount.js +107 -19
  118. package/dist/view/mount.js.map +1 -1
  119. package/dist/view/node-selection.d.ts.map +1 -1
  120. package/dist/view/node-selection.js +1 -3
  121. package/dist/view/node-selection.js.map +1 -1
  122. package/dist/view/render.d.ts.map +1 -1
  123. package/dist/view/render.js +10 -3
  124. package/dist/view/render.js.map +1 -1
  125. package/dist/widget/core.d.ts +37 -0
  126. package/dist/widget/core.d.ts.map +1 -0
  127. package/dist/widget/core.js +117 -0
  128. package/dist/widget/core.js.map +1 -0
  129. package/dist/widget/mount.d.ts +24 -0
  130. package/dist/widget/mount.d.ts.map +1 -0
  131. package/dist/widget/mount.js +45 -0
  132. package/dist/widget/mount.js.map +1 -0
  133. package/dist/widget/types.d.ts +44 -0
  134. package/dist/widget/types.d.ts.map +1 -0
  135. package/dist/widget/types.js +8 -0
  136. package/dist/widget/types.js.map +1 -0
  137. package/package.json +2 -6
@@ -0,0 +1,219 @@
1
+ import { isLeafPoint } from "./caret.js";
2
+ import { applyDomSelection, PATH_ATTR } from "./dom.js";
3
+ import { point, pointEq } from "../state/selection.js";
4
+ /** 셀렉션·포커스·오버레이 동기화 객체를 생성한다 */
5
+ export function createSelectionSync(deps) {
6
+ const { editor, el, dirtyPaths, readDomSel, ime } = deps;
7
+ /**
8
+ * 기능 DOM 오버레이 갱신 — 활성 줄 표시·괄호 하이라이트처럼
9
+ * 렌더 트리가 아니라 DOM을 직접 마킹하는 것들. 각 기능이
10
+ * Extension.input.paintOverlay로 구현하고 여기서 일괄 호출한다 —
11
+ * 어댑터 5개가 restoreSelection만 호출하면 동일 동작한다.
12
+ */
13
+ const paintOverlays = () => {
14
+ for (const h of editor.inputHooks) {
15
+ h.paintOverlay?.(el, editor.state, editor);
16
+ }
17
+ // atom 노드 셀렉션 시각화 — 선택 경로의 최외곽 요소에 is-selected를
18
+ // 단다. 각 기능의 오버레이(이미지 편집 바 등)와 별개로 공용으로 칠한다
19
+ el.querySelectorAll(".is-selected").forEach((n) => n.classList.remove("is-selected"));
20
+ const sel = editor.state.selection;
21
+ if (sel.type === "node") {
22
+ // data-me-path가 안쪽 아울렛(.me-node-content)에 달린 노드 뷰는
23
+ // 래퍼(.me-node-view)까지 올라가 컴포넌트 전체에 링을 둘러야 한다
24
+ const hit = el.querySelector(`[${PATH_ATTR}="${sel.path.join(",")}"]`);
25
+ (hit?.closest(".me-node-view") ?? hit)?.classList.add("is-selected");
26
+ }
27
+ };
28
+ /**
29
+ * 우리가 DOM에 마지막으로 반영했거나 selectionchange로 확인한 셀렉션.
30
+ * 이후 DOM이 이 값과 다르면 아직 동기화되지 않은 사용자 이동이다 —
31
+ * 비동기 렌더 커밋이 stale doc 셀렉션으로 캐럿을 되돌리는 레이스를
32
+ * 막기 위해 restoreSelection이 doc 대신 DOM을 진실로 택한다.
33
+ */
34
+ let lastApplied = null;
35
+ const applySel = (anchor, focus) => {
36
+ applyDomSelection(el, anchor, focus);
37
+ lastApplied = { anchor, focus };
38
+ };
39
+ // 포커스가 에디터 밖에 있는 동안 건너뛴 DOM 셀렉션 반영 —
40
+ // focusin에서 한 번에 동기화한다
41
+ let pendingSel = false;
42
+ const restoreSelection = () => {
43
+ // 더러운 블록이 같은 tr에서 지워져 어댑터가 소비하지 못한 엔트리는
44
+ // 버린다 — 인덱스가 재사용되면 엉뚱한 블록이 리마운트된다
45
+ const topLen = editor.state.doc.content?.length ?? 0;
46
+ for (const k of dirtyPaths) {
47
+ const i = Number(k);
48
+ if (!Number.isInteger(i) || i < 0 || i >= topLen) {
49
+ dirtyPaths.delete(k);
50
+ }
51
+ }
52
+ // compositionend~흡수 사이 창에는 DOM이 브라우저가 쓴 조합
53
+ // 텍스트를 품고 있다 — 지연된(비동기) 복원이 이 창에 떨어지면
54
+ // 캐럿을 되돌려 흡수가 목표 홀더를 놓친다. 흡수 뒤의 디스패치가
55
+ // 다시 복원을 트리거하므로 이 창만 건너뛴다 — 조합 중(composing)엔
56
+ // 복원이 필요하다: 어댑터의 선삭제 블록 리마운트는 비동기라
57
+ // composing이 선 뒤에 커밋되고, 리마운트가 분리시킨 DOM 캐럿을
58
+ // 새 DOM으로 재앵커하는 게 이 복원의 역할이다 (없으면 조합
59
+ // 세션이 유실돼 커밋이 통째로 날아간다 — 크로스-블록 조합
60
+ // e2e 회귀로 확인)
61
+ if (ime.absorbing)
62
+ return;
63
+ const sel = editor.state.selection;
64
+ // 포커스가 에디터 밖의 다른 요소(찾기 바·팝오버 입력)에 잡혀
65
+ // 있으면 DOM 셀렉션을 건드리지 않는다 — Blink는 contenteditable
66
+ // 안에 셀렉션을 쓰면 그 요소를 focus하므로, 매 커밋마다 입력
67
+ // 포커스를 빼앗긴다 (PM의 hasFocus 관례). 아무것도 포커스하지
68
+ // 않은 상태(body)는 기존처럼 반영한다 — 어차피 뺏을 포커스가
69
+ // 없고, DOM 셀렉션을 읽는 오버레이(ace 활성 라인)가 동작해야 한다
70
+ const ae = el.ownerDocument.activeElement;
71
+ if (ae && ae !== el.ownerDocument.body && !el.contains(ae)) {
72
+ pendingSel = true;
73
+ paintOverlays();
74
+ return;
75
+ }
76
+ pendingSel = false;
77
+ if (sel.type !== "text") {
78
+ // 노드 셀렉션 — DOM 캐럿을 노드 요소의 경계로 고정한다 (PM 관례).
79
+ // 고정하지 않으면 팝 닫힘·포커스 복귀 등으로 브라우저가 캐럿을
80
+ // 옮길 때 selectionchange가 그 위치를 읽어 노드 셀렉션을
81
+ // 되감는다 — 경계에 고정하면 이후 이벤트는 같은 위치를 읽는다
82
+ const hit = el.querySelector(`[${PATH_ATTR}="${sel.path.join(",")}"]`);
83
+ const host = hit?.closest(".me-node-view") ?? hit;
84
+ const pi = sel.path[sel.path.length - 1];
85
+ if (host?.parentNode && pi != null) {
86
+ const idx = Array.prototype.indexOf.call(host.parentNode.childNodes, host);
87
+ const ds = el.ownerDocument.getSelection();
88
+ ds?.setBaseAndExtent(host.parentNode, idx, host.parentNode, idx);
89
+ // 실제로 읽히는 Point를 기록 — childNodes 인덱스와 domToPoint의
90
+ // 요소 인덱스가 어긋나도 이후 selectionchange는 같은 값을 읽는다
91
+ const back = readDomSel();
92
+ const bp = point(sel.path.slice(0, -1), pi);
93
+ lastApplied = back ?? { anchor: bp, focus: bp };
94
+ }
95
+ paintOverlays();
96
+ return;
97
+ }
98
+ // 이미 DOM 셀렉션이 doc과 같으면 건드리지 않는다 (깜빡임/역순 방지)
99
+ const cur = readDomSel();
100
+ if (cur &&
101
+ pointEq(cur.anchor, sel.anchor) &&
102
+ pointEq(cur.focus, sel.focus)) {
103
+ paintOverlays();
104
+ return;
105
+ }
106
+ // DOM이 마지막 반영값과 다르다 = 사용자가 움직였지만 selectionchange가
107
+ // 아직 처리되지 않았다 — 비동기 렌더 커밋이 stale doc으로 캐럿을
108
+ // 되돌리는 레이스를 막는다. 단 두 조건이 모두 필요하다:
109
+ // - doc이 마지막 반영값 그대로 — 트랜잭션이 doc을 바꿨다면 그게
110
+ // 최신 의도라 DOM의 이전 위치는 버려야 한다(방금 dispatch된
111
+ // setSel이 먹지 않는 역효과 방지)
112
+ // - 리프 포인트 — 렌더가 노드를 교체하면 Chrome이 캐럿을 요소
113
+ // 경계로 재앵커하는데 그건 사용자 이동이 아니라 폐기 대상이다
114
+ if (cur &&
115
+ lastApplied &&
116
+ isLeafPoint(editor.state.doc, cur.anchor) &&
117
+ isLeafPoint(editor.state.doc, cur.focus) &&
118
+ pointEq(sel.anchor, lastApplied.anchor) &&
119
+ pointEq(sel.focus, lastApplied.focus) &&
120
+ (!pointEq(cur.anchor, lastApplied.anchor) ||
121
+ !pointEq(cur.focus, lastApplied.focus))) {
122
+ lastApplied = cur;
123
+ editor.dispatch(editor.state.tr().setSelection({ type: "text", ...cur }));
124
+ paintOverlays();
125
+ return;
126
+ }
127
+ applySel(sel.anchor, sel.focus);
128
+ paintOverlays();
129
+ };
130
+ const syncDocSelection = () => {
131
+ const s = readDomSel();
132
+ if (!s)
133
+ return;
134
+ // 흡수한 DOM 위치를 확인된 값으로 기록 — 이 위치에 대한 지연
135
+ // selectionchange(클릭 직후 키 입력 등)가 도착해도 "사용자가
136
+ // 새로 움직인 셀렉션"으로 오인하지 않게 한다 (노드 셀렉션 붕괴 방지)
137
+ lastApplied = s;
138
+ const cur = editor.state.selection;
139
+ if (cur.type === "text" &&
140
+ pointEq(cur.anchor, s.anchor) &&
141
+ pointEq(cur.focus, s.focus)) {
142
+ return;
143
+ }
144
+ // 대기 서식(storedMarks)은 커서 이동에도 유지한다 — GDocs/Word
145
+ // 관례: 토글로 끌 때까지 다음 타이핑에 계속 적용된다
146
+ editor.dispatch(editor.state.tr().setSelection({ type: "text", ...s }));
147
+ };
148
+ const onSelectionChange = () => {
149
+ if (ime.active)
150
+ return;
151
+ // 에디터 안의 셀렉션만 동기화 — 바깥 클릭은 무시
152
+ const sel = readDomSel();
153
+ if (!sel)
154
+ return;
155
+ // 노드 셀렉션 중 도착한 selectionchange — DOM 캐럿은 우리가 노드
156
+ // 경계에 고정한 앵커이거나 선택 전 stale 위치다. 노드 자체의
157
+ // 경계 포인트(앞/뒤)나 마지막 확인 위치와 같으면 사용자가 실제로
158
+ // 움직인 게 아니므로 노드 셀렉션을 유지한다.
159
+ // (DOM 위치가 바뀌었다면 진짜 클릭/이동 — 텍스트 셀렉션으로 붕괴)
160
+ if (editor.state.selection.type === "node") {
161
+ const np = editor.state.selection.path;
162
+ const ni = np[np.length - 1] ?? 0;
163
+ const nparent = np.slice(0, -1);
164
+ const atNodeBoundary = pointEq(sel.anchor, point(nparent, ni)) ||
165
+ pointEq(sel.anchor, point(nparent, ni + 1));
166
+ const unchanged = lastApplied &&
167
+ pointEq(lastApplied.anchor, sel.anchor) &&
168
+ pointEq(lastApplied.focus, sel.focus);
169
+ if (atNodeBoundary || unchanged) {
170
+ paintOverlays();
171
+ return;
172
+ }
173
+ }
174
+ // 확인한 DOM 위치를 기록 — 이후 변경분만 "사용자 이동"으로 본다
175
+ lastApplied = sel;
176
+ paintOverlays();
177
+ const cur = editor.state.selection;
178
+ const next = { type: "text", ...sel };
179
+ if (cur.type === "text" &&
180
+ pointEq(cur.anchor, next.anchor) &&
181
+ pointEq(cur.focus, next.focus)) {
182
+ return;
183
+ }
184
+ // 대기 서식(storedMarks)은 커서 이동에도 유지한다 — GDocs/Word 관례
185
+ editor.dispatch(editor.state.tr().setSelection(next));
186
+ };
187
+ // 포커스가 에디터로 돌아오면 건너뛴 DOM 셀렉션을 동기화한다 —
188
+ // 찾기 바에서 내비게이션한 뒤 닫히면 마지막 매치에 캐럿이 선다
189
+ const onFocusIn = (e) => {
190
+ if (pendingSel)
191
+ restoreSelection();
192
+ // focus는 "밖→안" 진입에서만 한 번 — 자식 간 이동(노드 뷰 내부
193
+ // 입력 등)은 focusin이 다시 떠도 focus 이벤트를 중복 발행하지 않는다
194
+ if (!focused) {
195
+ focused = true;
196
+ editor.emit("focus", { event: e });
197
+ }
198
+ };
199
+ // 포커스가 에디터 밖으로 나갈 때 blur — relatedTarget이 안쪽이면
200
+ // 자식 간 이동이라 무시한다. 협업 캐럿 표시 같은 외부 리스너가
201
+ // 이 이벤트로 리모트 커서를 지운다
202
+ const onFocusOut = (e) => {
203
+ const next = e.relatedTarget;
204
+ if (next && el.contains(next))
205
+ return;
206
+ focused = false;
207
+ editor.emit("blur", { event: e });
208
+ };
209
+ let focused = false;
210
+ return {
211
+ paintOverlays,
212
+ restoreSelection,
213
+ syncDocSelection,
214
+ onSelectionChange,
215
+ onFocusIn,
216
+ onFocusOut,
217
+ };
218
+ }
219
+ //# sourceMappingURL=input-selection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input-selection.js","sourceRoot":"","sources":["../../src/view/input-selection.ts"],"names":[],"mappings":"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"}
@@ -1,44 +1,53 @@
1
1
  import type { Editor } from "../editor/editor.js";
2
+ import type { ImeSession } from "./ime.js";
2
3
  /**
3
- * contenteditable ↔ 에디터 공용 입력 바인딩.
4
+ * contenteditable 바인딩 — DOM 이벤트 → 모델 트랜잭션 중계의
5
+ * 오케스트레이터. 각 어댑터는 이 함수만 호출한다 (PM domchange +
6
+ * domObserver + 5렌더러 공용 mountEditor 관례의 합성).
4
7
  *
5
- * 5개 렌더러가 공유하는 편집 루프:
6
- * DOM beforeinput → preventDefault → 트랜잭션 디스패치
7
- * (브라우저가 DOM을 직접 바꾸는 것을 막고 doc이 유일한 진실이 된다)
8
- * 렌더 커밋 후 → restoreSelection()으로 doc 셀렉션을 DOM에 반영
9
- * selectionchange → doc 셀렉션 동기화
10
- *
11
- * IME: composition 중 beforeinput/keydown은 전부 무시하고 브라우저가
12
- * DOM을 직접 바꾸게 둔다. compositionend에서 더러워진 텍스트 홀더를
13
- * doc과 diff해 차이만큼 delete+insert로 흡수한다. composing 해제는
14
- * setTimeout으로 지연 — Safari는 compositionend 뒤 같은 태스크에
15
- * 최종 beforeinput을 보내고, Chromium은 조합 확정 Enter의 keydown을
16
- * 같은 태스크에 내므로 둘 다 무시되어야 한다.
17
- *
18
- * 이 파일은 이벤트 수신부와 수명주기(IME 상태머신, 셀렉션 동기화)만
19
- * 담는다 — 판정/디스패치 로직은 형제 모듈로 분리됐다:
20
- * keys.ts — 키 문자열 정규화 + format* inputType 커맨드 매핑
21
- * caret.ts — DOM 캐럿 ↔ Point 판정 헬퍼
22
- * line-nav.ts — 시각적 줄 경계 이동 (Home/End·Mod-Arrow)
23
- * node-selection.ts — NodeSelection 상태의 beforeinput/keydown/클릭
24
- * hook-ctx.ts — 입력 훅 컨텍스트 팩토리 + 파일 훅 디스패치
25
- * dirty-keys.ts — DirtyKeyMap (어댑터 리마운트 키)
26
- * ime.ts — IME 조합 세션 상태머신 (흡수·커밋 에코 판별)
8
+ * 책임 분리(이 파일은 조립만 한다):
9
+ * input-selection.ts — 셀렉션 동기화·포커스·오버레이 페인트
10
+ * input-beforeinput.ts — beforeinput → 트랜잭션 디스패치
11
+ * input-keys.ts — keydown 키맵·Mod-A·노드 셀렉션 키·줄 내비
12
+ * input-pointer.ts — mousedown(마커/atom)·paste·dragover·drop
13
+ * keys.ts — 키 정규화·format* 커맨드 디스패치 테이블
14
+ * caret.ts — DOM 캐럿 ↔ Point 변환
15
+ * line-nav.ts — Home/End·Mod-Arrow 시각적 줄 내비게이션
16
+ * node-selection.ts — 노드 셀렉션의 beforeinput/keydown/클릭 처리
17
+ * hook-ctx.ts — 입력 훅 컨텍스트·파일 훅 디스패치
18
+ * dirty-keys.ts — 브라우저/IME가 범위 벗어난 변경을 어댑터가
19
+ * drain하는 더러운 키 맵
20
+ * ime.ts — IME 세션 상태 기계 (흡수·플러시·게이트)
27
21
  */
22
+ /** bindEditorInput이 반환하는 제어 핸들 */
28
23
  export interface EditorInputBinding {
24
+ /** 모든 DOM 리스너 해제 — 컴포넌트 언마운트 시 호출 */
29
25
  unbind(): void;
30
- /** 렌더 커밋 후 호출 — doc 셀렉션을 DOM 셀렉션으로 복원 */
26
+ /**
27
+ * 렌더 커밋 후 호출 — doc 셀렉션을 DOM 셀렉션으로 복원하고
28
+ * 기능 DOM 오버레이를 갱신한다
29
+ */
31
30
  restoreSelection(): void;
32
- /** DOM 셀렉션을 즉시 doc으로 동기화 */
31
+ /** DOM 셀렉션을 즉시 doc으로 동기화 (beforeinput 직전에 사용) */
33
32
  syncDocSelection(): void;
34
33
  /**
35
- * 외부(IME 등)가 프레임워크 몰래 직접 바꾼 최상위 블록 경로.
36
- * 브라우저가 변경한 DOM은 vnode와 어긋나므로 프레임워크 리컨실이
37
- * 깨진다 (React removeChild 크래시 등) — 어댑터는 렌더할 때
38
- * 해당 경로의 키 버전을 올려 블록을 통째 리마운트해야 한다.
39
- * drain 방식: 어댑터가 소비한 항목은 지운다.
34
+ * 브라우저/IME가 커밋된 트랜잭션 범위를 벗어나 변경한 최상위 블록
35
+ * 경로 — 어댑터가 리렌더 전에 drain해 해당 블록을 리마운트한다
40
36
  */
41
37
  readonly dirtyPaths: Set<string>;
38
+ /**
39
+ * IME 세션 — active(조합 중)/absorbing(흡수 대기) 동안 브라우저가
40
+ * DOM을 소유한다. 어댑터는 이 창의 파괴적 DOM 쓰기(dirty 리마운트·
41
+ * 전체 재구축)를 미뤄야 한다 — 지금 갈면 살아있는 조합 세션과
42
+ * 미흡수 커밋 텍스트가 파괴된다
43
+ */
44
+ readonly ime: ImeSession;
45
+ /**
46
+ * 흡수 flush 직후에 한 번 호출할 콜백 등록 — 조합 창에 미뤄둔
47
+ * DOM 쓰기의 커밋 지점. 흡수가 트랜잭션을 만들지 않아도 호출된다.
48
+ * 등록 해제 함수를 돌려준다
49
+ */
50
+ afterComposition(cb: () => void): () => void;
42
51
  }
43
52
  export declare function bindEditorInput(editor: Editor, el: HTMLElement): EditorInputBinding;
44
53
  export { DirtyKeyMap } from "./dirty-keys.js";
@@ -1 +1 @@
1
- {"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../src/view/input.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AA6BlD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,WAAW,kBAAkB;IACjC,MAAM,IAAI,IAAI,CAAC;IACf,yCAAyC;IACzC,gBAAgB,IAAI,IAAI,CAAC;IACzB,4BAA4B;IAC5B,gBAAgB,IAAI,IAAI,CAAC;IACzB;;;;;;OAMG;IACH,QAAQ,CAAC,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAClC;AAED,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,WAAW,GACd,kBAAkB,CAsuBpB;AAGD,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../src/view/input.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAGlD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAQ3C;;;;;;;;;;;;;;;;;;GAkBG;AAEH,kCAAkC;AAClC,MAAM,WAAW,kBAAkB;IACjC,qCAAqC;IACrC,MAAM,IAAI,IAAI,CAAC;IACf;;;OAGG;IACH,gBAAgB,IAAI,IAAI,CAAC;IACzB,iDAAiD;IACjD,gBAAgB,IAAI,IAAI,CAAC;IACzB;;;OAGG;IACH,QAAQ,CAAC,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACjC;;;;;OAKG;IACH,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC;IACzB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;CAC9C;AAED,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,WAAW,GACd,kBAAkB,CA8HpB;AAGD,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC"}