@moda-editor/core 0.1.5 → 0.2.0

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 (53) hide show
  1. package/dist/conformance/editor-base.d.ts.map +1 -1
  2. package/dist/conformance/editor-base.js +24 -0
  3. package/dist/conformance/editor-base.js.map +1 -1
  4. package/dist/conformance/editor-nodeview.d.ts.map +1 -1
  5. package/dist/conformance/editor-nodeview.js +49 -0
  6. package/dist/conformance/editor-nodeview.js.map +1 -1
  7. package/dist/conformance/editor-overlay.d.ts.map +1 -1
  8. package/dist/conformance/editor-overlay.js +23 -1
  9. package/dist/conformance/editor-overlay.js.map +1 -1
  10. package/dist/editor/editor.d.ts.map +1 -1
  11. package/dist/editor/editor.js +18 -1
  12. package/dist/editor/editor.js.map +1 -1
  13. package/dist/features/ace.d.ts.map +1 -1
  14. package/dist/features/ace.js +25 -7
  15. package/dist/features/ace.js.map +1 -1
  16. package/dist/features/code-block.d.ts.map +1 -1
  17. package/dist/features/code-block.js +47 -7
  18. package/dist/features/code-block.js.map +1 -1
  19. package/dist/features/doc.d.ts +16 -0
  20. package/dist/features/doc.d.ts.map +1 -1
  21. package/dist/features/doc.js +153 -1
  22. package/dist/features/doc.js.map +1 -1
  23. package/dist/features/image.d.ts +3 -1
  24. package/dist/features/image.d.ts.map +1 -1
  25. package/dist/features/image.js +16 -7
  26. package/dist/features/image.js.map +1 -1
  27. package/dist/features/search.d.ts +5 -2
  28. package/dist/features/search.d.ts.map +1 -1
  29. package/dist/features/search.js +42 -13
  30. package/dist/features/search.js.map +1 -1
  31. package/dist/index.d.ts +2 -0
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +2 -0
  34. package/dist/index.js.map +1 -1
  35. package/dist/presets/markdown.d.ts +2 -1
  36. package/dist/presets/markdown.d.ts.map +1 -1
  37. package/dist/presets/markdown.js +3 -2
  38. package/dist/presets/markdown.js.map +1 -1
  39. package/dist/state/apply.d.ts.map +1 -1
  40. package/dist/state/apply.js +6 -2
  41. package/dist/state/apply.js.map +1 -1
  42. package/dist/styles.css +253 -196
  43. package/dist/theme.d.ts +29 -0
  44. package/dist/theme.d.ts.map +1 -0
  45. package/dist/theme.js +347 -0
  46. package/dist/theme.js.map +1 -0
  47. package/dist/view/input.d.ts.map +1 -1
  48. package/dist/view/input.js +75 -3
  49. package/dist/view/input.js.map +1 -1
  50. package/dist/view/render.d.ts.map +1 -1
  51. package/dist/view/render.js +9 -0
  52. package/dist/view/render.js.map +1 -1
  53. package/package.json +1 -1
@@ -0,0 +1,29 @@
1
+ /**
2
+ * 테마 — 에디터 크롬/본문의 --me-* 변수를 한 묶음으로 바꾼다.
3
+ * 테마는 플랫한 변수 맵이라 소비자가 객체 리터럴 하나로 직접
4
+ * 디자인할 수 있다 — CSS 파일을 고치지 않고 런타임에 적용된다.
5
+ *
6
+ * 적용 지점은 허브 3곳 — .me-frame(크롬: 툴바/상태바/팝오버),
7
+ * .me-root, .me-editor(본문/토큰 팔레트). 리프 요소들은 변수를
8
+ * 자체 정의하지 않고 var()로만 소비하므로 허브에만 박으면
9
+ * 나머지는 상속으로 따라온다 (styles.css 주석 참고).
10
+ */
11
+ /** 테마 — vars는 { "--me-변수명": 값 } 맵. scheme은 다크 테마의
12
+ * 스크롤바/폼 컨트롤을 어둡게 만드는 color-scheme 힌트 */
13
+ export interface MeTheme {
14
+ id: string;
15
+ label: string;
16
+ scheme?: "light" | "dark";
17
+ vars: Record<string, string>;
18
+ }
19
+ /** 내장 테마 — vars에 없는 변수는 기본(라이트) 값이 그대로 적용된다 */
20
+ export declare const ME_THEMES: readonly MeTheme[];
21
+ /** id로 내장 테마를 찾는다 — 없거나 빈 값이면 null(기본 테마) */
22
+ export declare function themeById(id: string | null | undefined): MeTheme | null;
23
+ /**
24
+ * 테마 적용 — el(프레임이나 그 안의 아무 요소)이 속한 허브에 변수를
25
+ * 박는다. theme이 null이면 이전 테마를 지워 기본 팔레트로 돌아간다.
26
+ * 스코프 안에 .me-frame이 없으면 el 자체가 스코프다(단독 .me-root 사용).
27
+ */
28
+ export declare function applyTheme(el: Element, theme: MeTheme | null): void;
29
+ //# sourceMappingURL=theme.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../src/theme.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH;0CAC0C;AAC1C,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAED,+CAA+C;AAC/C,eAAO,MAAM,SAAS,EAAE,SAAS,OAAO,EAqSvC,CAAC;AAEF,6CAA6C;AAC7C,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,GAAG,IAAI,CAEvE;AAMD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,GAAG,IAAI,CAsBnE"}
package/dist/theme.js ADDED
@@ -0,0 +1,347 @@
1
+ /**
2
+ * 테마 — 에디터 크롬/본문의 --me-* 변수를 한 묶음으로 바꾼다.
3
+ * 테마는 플랫한 변수 맵이라 소비자가 객체 리터럴 하나로 직접
4
+ * 디자인할 수 있다 — CSS 파일을 고치지 않고 런타임에 적용된다.
5
+ *
6
+ * 적용 지점은 허브 3곳 — .me-frame(크롬: 툴바/상태바/팝오버),
7
+ * .me-root, .me-editor(본문/토큰 팔레트). 리프 요소들은 변수를
8
+ * 자체 정의하지 않고 var()로만 소비하므로 허브에만 박으면
9
+ * 나머지는 상속으로 따라온다 (styles.css 주석 참고).
10
+ */
11
+ /** 내장 테마 — vars에 없는 변수는 기본(라이트) 값이 그대로 적용된다 */
12
+ export const ME_THEMES = [
13
+ {
14
+ // 심플 — 무채색 미니멀. 액센트까지 회색으로 눌러 본문만 남긴다
15
+ id: "simple",
16
+ label: "심플",
17
+ vars: {
18
+ "--me-frame-bg": "#ffffff",
19
+ "--me-border-color": "#e4e4e7",
20
+ "--me-text-color": "#18181b",
21
+ "--me-editor-bg": "#ffffff",
22
+ "--me-editor-focus": "#52525b",
23
+ "--me-sel-bg": "rgba(24, 24, 27, 0.08)",
24
+ "--me-gutter-bg": "rgba(24, 24, 27, 0.03)",
25
+ "--me-gutter-color": "#a1a1aa",
26
+ "--me-gutter-border": "#e4e4e7",
27
+ "--me-line-active-num": "#52525b",
28
+ "--me-node-sel-color": "#52525b",
29
+ "--me-print-margin": "rgba(161, 161, 170, 0.4)",
30
+ "--me-toolbar-bg": "#fafafa",
31
+ "--me-tool-hover-bg": "#e4e4e7",
32
+ "--me-tool-active-bg": "#e4e4e7",
33
+ "--me-tool-active-color": "#18181b",
34
+ "--me-status-color": "#a1a1aa",
35
+ "--me-btn-bg": "#f4f4f5",
36
+ "--me-btn-hover-bg": "#e4e4e7",
37
+ "--me-accent": "#52525b",
38
+ "--me-text-muted": "#71717a",
39
+ "--me-link-color": "#18181b",
40
+ "--me-quote-color": "#71717a",
41
+ "--me-placeholder-color": "#a1a1aa",
42
+ "--me-code-bg": "#f4f4f5",
43
+ "--me-code-border": "#e4e4e7",
44
+ "--me-callout-bg": "#fafafa",
45
+ "--me-callout-border": "#e4e4e7",
46
+ "--me-callout-accent": "#71717a",
47
+ "--me-checkbox-border": "#a1a1aa",
48
+ "--me-checkbox-checked": "#18181b",
49
+ // 심플의 코드 팔레트도 톤 다운 — 색 대신 명암으로 구분
50
+ "--me-tok-keyword": "#18181b",
51
+ "--me-tok-string": "#3f3f46",
52
+ "--me-tok-comment": "#a1a1aa",
53
+ "--me-tok-number": "#3f3f46",
54
+ "--me-tok-literal": "#3f3f46",
55
+ "--me-tok-function": "#18181b",
56
+ "--me-tok-tag": "#3f3f46",
57
+ "--me-tok-attr": "#71717a",
58
+ },
59
+ },
60
+ {
61
+ // 모던 — 인디고 액센트 + 부드러운 그림자 (Linear/현대 SaaS 관례)
62
+ id: "modern",
63
+ label: "모던",
64
+ vars: {
65
+ "--me-frame-bg": "#ffffff",
66
+ "--me-frame-shadow": "0 8px 30px rgba(79, 70, 229, 0.08)",
67
+ "--me-border-color": "#dfe5f5",
68
+ "--me-text-color": "#0f172a",
69
+ "--me-editor-bg": "#ffffff",
70
+ "--me-editor-focus": "#6366f1",
71
+ "--me-sel-bg": "rgba(99, 102, 241, 0.16)",
72
+ "--me-gutter-bg": "rgba(99, 102, 241, 0.04)",
73
+ "--me-gutter-color": "#94a3b8",
74
+ "--me-gutter-border": "#dfe5f5",
75
+ "--me-line-active-num": "#4f46e5",
76
+ "--me-node-sel-color": "#6366f1",
77
+ "--me-print-margin": "rgba(99, 102, 241, 0.3)",
78
+ "--me-toolbar-bg": "#f8f9ff",
79
+ "--me-tool-hover-bg": "#e8ecff",
80
+ "--me-tool-active-bg": "#e0e7ff",
81
+ "--me-tool-active-color": "#4f46e5",
82
+ "--me-status-color": "#64748b",
83
+ "--me-btn-bg": "#eef2ff",
84
+ "--me-btn-hover-bg": "#e0e7ff",
85
+ "--me-accent": "#6366f1",
86
+ "--me-text-muted": "#64748b",
87
+ "--me-link-color": "#4f46e5",
88
+ "--me-quote-color": "#64748b",
89
+ "--me-placeholder-color": "#a5b4fc",
90
+ "--me-code-bg": "#f1f5ff",
91
+ "--me-code-border": "#dfe5f5",
92
+ "--me-code-active-bg": "rgba(99, 102, 241, 0.07)",
93
+ "--me-callout-bg": "#eef2ff",
94
+ "--me-callout-border": "#c7d2fe",
95
+ "--me-callout-accent": "#6366f1",
96
+ "--me-checkbox-border": "#a5b4fc",
97
+ "--me-checkbox-checked": "#4f46e5",
98
+ "--me-pop-shadow": "0 12px 32px rgba(79, 70, 229, 0.16)",
99
+ "--me-tok-keyword": "#7c3aed",
100
+ "--me-tok-string": "#0e7490",
101
+ "--me-tok-comment": "#94a3b8",
102
+ "--me-tok-number": "#db2777",
103
+ "--me-tok-literal": "#db2777",
104
+ "--me-tok-function": "#2563eb",
105
+ "--me-tok-tag": "#7c3aed",
106
+ "--me-tok-attr": "#0891b2",
107
+ },
108
+ },
109
+ {
110
+ // 다크 — Ace Monokai 계열 (기존 [data-me-theme="dark"]와 동일 팔레트)
111
+ id: "dark",
112
+ label: "다크",
113
+ scheme: "dark",
114
+ vars: {
115
+ "--me-frame-bg": "#1e1f1c",
116
+ "--me-border-color": "#3e3d32",
117
+ "--me-text-color": "#f8f8f2",
118
+ "--me-editor-bg": "#272822",
119
+ "--me-editor-focus": "#66d9ef",
120
+ "--me-sel-bg": "rgba(73, 72, 62, 0.95)",
121
+ "--me-gutter-bg": "rgba(0, 0, 0, 0.22)",
122
+ "--me-gutter-color": "#8f908a",
123
+ "--me-gutter-border": "#3e3d32",
124
+ "--me-line-active-num": "#c2c2bf",
125
+ "--me-node-sel-color": "#66d9ef",
126
+ "--me-print-margin": "rgba(73, 72, 62, 0.9)",
127
+ "--me-toolbar-bg": "#1e1f1c",
128
+ "--me-tool-hover-bg": "#3e3d32",
129
+ "--me-tool-active-bg": "rgba(102, 217, 239, 0.14)",
130
+ "--me-tool-active-color": "#66d9ef",
131
+ "--me-status-color": "#8f908a",
132
+ "--me-btn-bg": "#3e3d32",
133
+ "--me-btn-hover-bg": "#575643",
134
+ "--me-accent": "#66d9ef",
135
+ "--me-text-muted": "#8f908a",
136
+ "--me-link-color": "#66d9ef",
137
+ "--me-quote-color": "#a09f93",
138
+ "--me-placeholder-color": "#6a6b60",
139
+ "--me-code-bg": "#1e1f1c",
140
+ "--me-code-border": "#3e3d32",
141
+ "--me-code-active-bg": "rgba(255, 255, 255, 0.05)",
142
+ "--me-callout-bg": "#2d2e25",
143
+ "--me-callout-border": "#575643",
144
+ "--me-callout-accent": "#66d9ef",
145
+ "--me-checkbox-border": "#575643",
146
+ "--me-checkbox-checked": "#a6e22e",
147
+ "--me-pop-shadow": "0 8px 24px rgba(0, 0, 0, 0.5)",
148
+ "--me-tok-keyword": "#f92672",
149
+ "--me-tok-string": "#e6db74",
150
+ "--me-tok-comment": "#75715e",
151
+ "--me-tok-number": "#ae81ff",
152
+ "--me-tok-literal": "#ae81ff",
153
+ "--me-tok-function": "#a6e22e",
154
+ "--me-tok-tag": "#f92672",
155
+ "--me-tok-attr": "#a6e22e",
156
+ },
157
+ },
158
+ {
159
+ // 네온 다크 — 검정 바탕에 사이언/마젠타 발광 (Synthwave 관례)
160
+ id: "neon",
161
+ label: "네온 다크",
162
+ scheme: "dark",
163
+ vars: {
164
+ "--me-frame-bg": "#0b0b16",
165
+ "--me-frame-shadow": "0 0 30px rgba(0, 229, 255, 0.12)",
166
+ "--me-border-color": "#22223a",
167
+ "--me-text-color": "#d9faff",
168
+ "--me-editor-bg": "#07070f",
169
+ "--me-editor-focus": "#00e5ff",
170
+ "--me-sel-bg": "rgba(0, 229, 255, 0.18)",
171
+ "--me-gutter-bg": "rgba(0, 229, 255, 0.06)",
172
+ "--me-gutter-color": "#3f6b76",
173
+ "--me-gutter-border": "#22223a",
174
+ "--me-line-active-num": "#00e5ff",
175
+ "--me-node-sel-color": "#00e5ff",
176
+ "--me-print-margin": "rgba(0, 229, 255, 0.15)",
177
+ "--me-toolbar-bg": "#0b0b16",
178
+ "--me-tool-hover-bg": "#1a1a2e",
179
+ "--me-tool-active-bg": "rgba(0, 229, 255, 0.15)",
180
+ "--me-tool-active-color": "#00e5ff",
181
+ "--me-status-color": "#4a6b78",
182
+ "--me-btn-bg": "#1a1a2e",
183
+ "--me-btn-hover-bg": "#22223f",
184
+ "--me-accent": "#00e5ff",
185
+ "--me-text-muted": "#4a6b78",
186
+ "--me-link-color": "#00e5ff",
187
+ "--me-quote-color": "#6b8b96",
188
+ "--me-placeholder-color": "#3a4a55",
189
+ "--me-code-bg": "#07070f",
190
+ "--me-code-border": "#22223a",
191
+ "--me-code-active-bg": "rgba(0, 229, 255, 0.07)",
192
+ "--me-callout-bg": "#101022",
193
+ "--me-callout-border": "#2a2a55",
194
+ "--me-callout-accent": "#00e5ff",
195
+ "--me-checkbox-border": "#2a2a55",
196
+ "--me-checkbox-checked": "#ff2e97",
197
+ "--me-pop-shadow": "0 0 24px rgba(0, 229, 255, 0.22)",
198
+ "--me-tok-keyword": "#ff2e97",
199
+ "--me-tok-string": "#f0ff6b",
200
+ "--me-tok-comment": "#4a6b78",
201
+ "--me-tok-number": "#b76cff",
202
+ "--me-tok-literal": "#b76cff",
203
+ "--me-tok-function": "#00e5ff",
204
+ "--me-tok-tag": "#ff2e97",
205
+ "--me-tok-attr": "#c3ff6b",
206
+ },
207
+ },
208
+ {
209
+ // 퍼플 — 라이트 라벤더. 바이올렛 액센트의 부드러운 라이트 테마
210
+ id: "purple",
211
+ label: "퍼플",
212
+ vars: {
213
+ "--me-frame-bg": "#fbf9ff",
214
+ "--me-border-color": "#e4dbf7",
215
+ "--me-text-color": "#2e2145",
216
+ "--me-editor-bg": "#fdfcff",
217
+ "--me-editor-focus": "#7c3aed",
218
+ "--me-sel-bg": "rgba(124, 58, 237, 0.14)",
219
+ "--me-gutter-bg": "rgba(124, 58, 237, 0.05)",
220
+ "--me-gutter-color": "#a594c7",
221
+ "--me-gutter-border": "#e4dbf7",
222
+ "--me-line-active-num": "#7c3aed",
223
+ "--me-node-sel-color": "#7c3aed",
224
+ "--me-print-margin": "rgba(124, 58, 237, 0.25)",
225
+ "--me-toolbar-bg": "#f4efff",
226
+ "--me-tool-hover-bg": "#eae2fb",
227
+ "--me-tool-active-bg": "#e6ddfb",
228
+ "--me-tool-active-color": "#6d28d9",
229
+ "--me-status-color": "#8b7ab8",
230
+ "--me-btn-bg": "#ece5fb",
231
+ "--me-btn-hover-bg": "#ded1f8",
232
+ "--me-accent": "#7c3aed",
233
+ "--me-text-muted": "#8b7ab8",
234
+ "--me-link-color": "#7c3aed",
235
+ "--me-quote-color": "#8b7ab8",
236
+ "--me-placeholder-color": "#b3a1d6",
237
+ "--me-code-bg": "#f5f0ff",
238
+ "--me-code-border": "#e4dbf7",
239
+ "--me-code-active-bg": "rgba(124, 58, 237, 0.07)",
240
+ "--me-callout-bg": "#f3edff",
241
+ "--me-callout-border": "#d9cdf3",
242
+ "--me-callout-accent": "#7c3aed",
243
+ "--me-checkbox-border": "#c4b5fd",
244
+ "--me-checkbox-checked": "#7c3aed",
245
+ "--me-pop-shadow": "0 10px 30px rgba(124, 58, 237, 0.16)",
246
+ "--me-tok-keyword": "#7c3aed",
247
+ "--me-tok-string": "#0e7490",
248
+ "--me-tok-comment": "#a594c7",
249
+ "--me-tok-number": "#c026d3",
250
+ "--me-tok-literal": "#c026d3",
251
+ "--me-tok-function": "#4f46e5",
252
+ "--me-tok-tag": "#7c3aed",
253
+ "--me-tok-attr": "#0e7490",
254
+ },
255
+ },
256
+ {
257
+ // 퍼플 다크 — 딥 바이올렛 바탕 + 라벤더 액센트
258
+ id: "grape",
259
+ label: "퍼플 다크",
260
+ scheme: "dark",
261
+ vars: {
262
+ "--me-frame-bg": "#1b1230",
263
+ "--me-border-color": "#3b2d5e",
264
+ "--me-text-color": "#ece5fb",
265
+ "--me-editor-bg": "#150e24",
266
+ "--me-editor-focus": "#c084fc",
267
+ "--me-sel-bg": "rgba(192, 132, 252, 0.22)",
268
+ "--me-gutter-bg": "rgba(0, 0, 0, 0.3)",
269
+ "--me-gutter-color": "#6b5a95",
270
+ "--me-gutter-border": "#3b2d5e",
271
+ "--me-line-active-num": "#d8b4fe",
272
+ "--me-node-sel-color": "#c084fc",
273
+ "--me-print-margin": "rgba(192, 132, 252, 0.2)",
274
+ "--me-toolbar-bg": "#1b1230",
275
+ "--me-tool-hover-bg": "#2d2049",
276
+ "--me-tool-active-bg": "rgba(192, 132, 252, 0.16)",
277
+ "--me-tool-active-color": "#d8b4fe",
278
+ "--me-status-color": "#8b7ab8",
279
+ "--me-btn-bg": "#2d2049",
280
+ "--me-btn-hover-bg": "#3b2d5e",
281
+ "--me-accent": "#c084fc",
282
+ "--me-text-muted": "#8b7ab8",
283
+ "--me-link-color": "#d8b4fe",
284
+ "--me-quote-color": "#9d8cc4",
285
+ "--me-placeholder-color": "#6b5a95",
286
+ "--me-code-bg": "#170f2a",
287
+ "--me-code-border": "#3b2d5e",
288
+ "--me-code-active-bg": "rgba(192, 132, 252, 0.08)",
289
+ "--me-callout-bg": "#221740",
290
+ "--me-callout-border": "#4a3575",
291
+ "--me-callout-accent": "#c084fc",
292
+ "--me-checkbox-border": "#4a3575",
293
+ "--me-checkbox-checked": "#c084fc",
294
+ "--me-pop-shadow": "0 8px 28px rgba(0, 0, 0, 0.55)",
295
+ "--me-tok-keyword": "#e879f9",
296
+ "--me-tok-string": "#bef264",
297
+ "--me-tok-comment": "#6b5a95",
298
+ "--me-tok-number": "#f9a8d4",
299
+ "--me-tok-literal": "#f9a8d4",
300
+ "--me-tok-function": "#a78bfa",
301
+ "--me-tok-tag": "#e879f9",
302
+ "--me-tok-attr": "#67e8f9",
303
+ },
304
+ },
305
+ ];
306
+ /** id로 내장 테마를 찾는다 — 없거나 빈 값이면 null(기본 테마) */
307
+ export function themeById(id) {
308
+ return ME_THEMES.find((t) => t.id === id) ?? null;
309
+ }
310
+ /* applyTheme이 마지막으로 각 허브에 박은 변수명 — 테마 전환/해제 시
311
+ * 이전 흔적을 정확히 지운다 (지우지 않으면 이전 테마의 변수가 남는다) */
312
+ const APPLIED = new WeakMap();
313
+ /**
314
+ * 테마 적용 — el(프레임이나 그 안의 아무 요소)이 속한 허브에 변수를
315
+ * 박는다. theme이 null이면 이전 테마를 지워 기본 팔레트로 돌아간다.
316
+ * 스코프 안에 .me-frame이 없으면 el 자체가 스코프다(단독 .me-root 사용).
317
+ */
318
+ export function applyTheme(el, theme) {
319
+ const scope = el.closest(".me-frame") ?? el;
320
+ // 허브 — 크롬(프레임/툴바/팝), 본문 래퍼, 에디터. 리프는 변수를
321
+ // 자체 정의하지 않으므로 여기에만 박으면 상속으로 전부 도달한다
322
+ const hubs = new Set([scope]);
323
+ if (scope.matches(".me-root, .me-editor"))
324
+ hubs.add(scope);
325
+ for (const q of [".me-root", ".me-editor"]) {
326
+ const hit = scope.querySelector(q);
327
+ if (hit)
328
+ hubs.add(hit);
329
+ }
330
+ const vars = theme?.vars ?? {};
331
+ for (const hub of hubs) {
332
+ const prev = APPLIED.get(hub) ?? [];
333
+ const style = hub.style;
334
+ for (const k of prev)
335
+ style.removeProperty(k);
336
+ const next = Object.keys(vars);
337
+ for (const k of next)
338
+ style.setProperty(k, vars[k]);
339
+ APPLIED.set(hub, next);
340
+ }
341
+ if (theme)
342
+ scope.setAttribute("data-me-theme", theme.id);
343
+ else
344
+ scope.removeAttribute("data-me-theme");
345
+ scope.style.colorScheme = theme?.scheme ?? "";
346
+ }
347
+ //# sourceMappingURL=theme.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.js","sourceRoot":"","sources":["../src/theme.ts"],"names":[],"mappings":"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"}
@@ -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;AAuBlD;;;;;;;;;;;;;;;;;;;;;;;;;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,CAylBpB;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;AA4BlD;;;;;;;;;;;;;;;;;;;;;;;;;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,CAwqBpB;AAGD,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC"}
@@ -1,7 +1,7 @@
1
1
  import { nodeAt } from "../model/traverse.js";
2
2
  import { parseHTML } from "../parse/html.js";
3
3
  import { pointBefore, pointAfter } from "../state/navigate.js";
4
- import { normalizeRange, pointEq, textSelection } from "../state/selection.js";
4
+ import { normalizeRange, point, pointEq, textSelection, } from "../state/selection.js";
5
5
  import { caretPointFromXY, isLeafPoint } from "./caret.js";
6
6
  import { applyDomSelection, domSelection, domToPoint, PATH_ATTR, } from "./dom.js";
7
7
  import { blockCtx, dispatchFiles, rangeCtx } from "./hook-ctx.js";
@@ -31,7 +31,10 @@ export function bindEditorInput(editor, el) {
31
31
  el.querySelectorAll(".is-selected").forEach((n) => n.classList.remove("is-selected"));
32
32
  const sel = editor.state.selection;
33
33
  if (sel.type === "node") {
34
- el.querySelector(`[${PATH_ATTR}="${sel.path.join(",")}"]`)?.classList.add("is-selected");
34
+ // data-me-path가 안쪽 아울렛(.me-node-content)에 달린 노드 뷰는
35
+ // 래퍼(.me-node-view)까지 올라가 컴포넌트 전체에 링을 둘러야 한다
36
+ const hit = el.querySelector(`[${PATH_ATTR}="${sel.path.join(",")}"]`);
37
+ (hit?.closest(".me-node-view") ?? hit)?.classList.add("is-selected");
35
38
  }
36
39
  };
37
40
  /**
@@ -45,10 +48,42 @@ export function bindEditorInput(editor, el) {
45
48
  applyDomSelection(el, anchor, focus);
46
49
  lastApplied = { anchor, focus };
47
50
  };
51
+ // 포커스가 에디터 밖에 있는 동안 건너뛴 DOM 셀렉션 반영 —
52
+ // focusin에서 한 번에 동기화한다
53
+ let pendingSel = false;
48
54
  const restoreSelection = () => {
49
55
  const sel = editor.state.selection;
50
- // 노드 셀렉션은 DOM 캐럿이 없다 — 오버레이만 갱신하고 끝낸다
56
+ // 포커스가 에디터 밖의 다른 요소(찾기 바·팝오버 입력)에 잡혀
57
+ // 있으면 DOM 셀렉션을 건드리지 않는다 — Blink는 contenteditable
58
+ // 안에 셀렉션을 쓰면 그 요소를 focus하므로, 매 커밋마다 입력
59
+ // 포커스를 빼앗긴다 (PM의 hasFocus 관례). 아무것도 포커스하지
60
+ // 않은 상태(body)는 기존처럼 반영한다 — 어차피 뺏을 포커스가
61
+ // 없고, DOM 셀렉션을 읽는 오버레이(ace 활성 라인)가 동작해야 한다
62
+ const ae = el.ownerDocument.activeElement;
63
+ if (ae && ae !== el.ownerDocument.body && !el.contains(ae)) {
64
+ pendingSel = true;
65
+ paintOverlays();
66
+ return;
67
+ }
68
+ pendingSel = false;
51
69
  if (sel.type !== "text") {
70
+ // 노드 셀렉션 — DOM 캐럿을 노드 요소의 경계로 고정한다 (PM 관례).
71
+ // 고정하지 않으면 팝 닫힘·포커스 복귀 등으로 브라우저가 캐럿을
72
+ // 옮길 때 selectionchange가 그 위치를 읽어 노드 셀렉션을
73
+ // 되감는다 — 경계에 고정하면 이후 이벤트는 같은 위치를 읽는다
74
+ const hit = el.querySelector(`[${PATH_ATTR}="${sel.path.join(",")}"]`);
75
+ const host = hit?.closest(".me-node-view") ?? hit;
76
+ const pi = sel.path[sel.path.length - 1];
77
+ if (host?.parentNode && pi != null) {
78
+ const idx = Array.prototype.indexOf.call(host.parentNode.childNodes, host);
79
+ const ds = el.ownerDocument.getSelection();
80
+ ds?.setBaseAndExtent(host.parentNode, idx, host.parentNode, idx);
81
+ // 실제로 읽히는 Point를 기록 — childNodes 인덱스와 domToPoint의
82
+ // 요소 인덱스가 어긋나도 이후 selectionchange는 같은 값을 읽는다
83
+ const back = readDomSel();
84
+ const bp = point(sel.path.slice(0, -1), pi);
85
+ lastApplied = back ?? { anchor: bp, focus: bp };
86
+ }
52
87
  paintOverlays();
53
88
  return;
54
89
  }
@@ -88,6 +123,10 @@ export function bindEditorInput(editor, el) {
88
123
  const s = readDomSel();
89
124
  if (!s)
90
125
  return;
126
+ // 흡수한 DOM 위치를 확인된 값으로 기록 — 이 위치에 대한 지연
127
+ // selectionchange(클릭 직후 키 입력 등)가 도착해도 "사용자가
128
+ // 새로 움직인 셀렉션"으로 오인하지 않게 한다 (노드 셀렉션 붕괴 방지)
129
+ lastApplied = s;
91
130
  const cur = editor.state.selection;
92
131
  if (cur.type === "text" &&
93
132
  pointEq(cur.anchor, s.anchor) &&
@@ -427,6 +466,12 @@ export function bindEditorInput(editor, el) {
427
466
  .setSelection(textSelection({ path: [], offset: 0 }, { path: [], offset: editor.state.doc.content?.length ?? 0 })));
428
467
  return;
429
468
  }
469
+ // 클릭 직후 키 입력처럼 selectionchange가 아직 도착하지 않은 창에는
470
+ // doc 셀렉션이 stale하다 — PM domObserver.flush 관례로 키 처리 전에
471
+ // DOM 셀렉션을 먼저 흡수한다. 노드 셀렉션 중엔 DOM 캐럿이 무의미한
472
+ // stale 위치라 덮어쓰지 않는다
473
+ if (editor.state.selection.type !== "node")
474
+ syncDocSelection();
430
475
  // 노드 셀렉션(이미지 클릭 등) — DOM 캐럿이 없으므로 네이티브
431
476
  // 키 동작은 모두 무시하고 모델에서 처리한다. Backspace/Delete는
432
477
  // 노드 삭제, Enter는 노드 뒤에 새 블록, 화살표/Escape는 텍스트
@@ -536,6 +581,25 @@ export function bindEditorInput(editor, el) {
536
581
  const sel = readDomSel();
537
582
  if (!sel)
538
583
  return;
584
+ // 노드 셀렉션 중 도착한 selectionchange — DOM 캐럿은 우리가 노드
585
+ // 경계에 고정한 앵커이거나 선택 전 stale 위치다. 노드 자체의
586
+ // 경계 포인트(앞/뒤)나 마지막 확인 위치와 같으면 사용자가 실제로
587
+ // 움직인 게 아니므로 노드 셀렉션을 유지한다.
588
+ // (DOM 위치가 바뀌었다면 진짜 클릭/이동 — 텍스트 셀렉션으로 붕괴)
589
+ if (editor.state.selection.type === "node") {
590
+ const np = editor.state.selection.path;
591
+ const ni = np[np.length - 1] ?? 0;
592
+ const nparent = np.slice(0, -1);
593
+ const atNodeBoundary = pointEq(sel.anchor, point(nparent, ni)) ||
594
+ pointEq(sel.anchor, point(nparent, ni + 1));
595
+ const unchanged = lastApplied &&
596
+ pointEq(lastApplied.anchor, sel.anchor) &&
597
+ pointEq(lastApplied.focus, sel.focus);
598
+ if (atNodeBoundary || unchanged) {
599
+ paintOverlays();
600
+ return;
601
+ }
602
+ }
539
603
  // 확인한 DOM 위치를 기록 — 이후 변경분만 "사용자 이동"으로 본다
540
604
  lastApplied = sel;
541
605
  paintOverlays();
@@ -549,6 +613,12 @@ export function bindEditorInput(editor, el) {
549
613
  // 대기 서식(storedMarks)은 커서 이동에도 유지한다 — GDocs/Word 관례
550
614
  editor.dispatch(editor.state.tr().setSelection(next));
551
615
  };
616
+ // 포커스가 에디터로 돌아오면 건너뛴 DOM 셀렉션을 동기화한다 —
617
+ // 찾기 바에서 내비게이션한 뒤 닫히면 마지막 매치에 캐럿이 선다
618
+ const onFocusIn = () => {
619
+ if (pendingSel)
620
+ restoreSelection();
621
+ };
552
622
  el.addEventListener("beforeinput", onBeforeInput);
553
623
  el.addEventListener("keydown", onKeydown);
554
624
  el.addEventListener("mousedown", onMouseDown);
@@ -557,6 +627,7 @@ export function bindEditorInput(editor, el) {
557
627
  el.addEventListener("drop", onDrop);
558
628
  el.addEventListener("compositionstart", ime.start);
559
629
  el.addEventListener("compositionend", ime.end);
630
+ el.addEventListener("focusin", onFocusIn);
560
631
  el.ownerDocument.addEventListener("selectionchange", onSelectionChange);
561
632
  // 오버레이 초기 페인트 — React/Vue2처럼 마운트 layoutEffect가
562
633
  // 바인딩보다 먼저 도는 렌더러에서도 첫 커밋 전에 오버레이가
@@ -577,6 +648,7 @@ export function bindEditorInput(editor, el) {
577
648
  el.removeEventListener("drop", onDrop);
578
649
  el.removeEventListener("compositionstart", ime.start);
579
650
  el.removeEventListener("compositionend", ime.end);
651
+ el.removeEventListener("focusin", onFocusIn);
580
652
  el.ownerDocument.removeEventListener("selectionchange", onSelectionChange);
581
653
  },
582
654
  restoreSelection,