@moda-editor/core 0.2.3 → 0.3.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.
Files changed (99) hide show
  1. package/dist/collab.d.ts.map +1 -1
  2. package/dist/collab.js +14 -18
  3. package/dist/collab.js.map +1 -1
  4. package/dist/commands.d.ts.map +1 -1
  5. package/dist/commands.js +24 -4
  6. package/dist/commands.js.map +1 -1
  7. package/dist/conformance/editor-base.d.ts.map +1 -1
  8. package/dist/conformance/editor-base.js +57 -0
  9. package/dist/conformance/editor-base.js.map +1 -1
  10. package/dist/conformance/editor-ime.d.ts.map +1 -1
  11. package/dist/conformance/editor-ime.js +78 -0
  12. package/dist/conformance/editor-ime.js.map +1 -1
  13. package/dist/conformance/fixtures.d.ts +2 -2
  14. package/dist/conformance/fixtures.d.ts.map +1 -1
  15. package/dist/conformance/fixtures.js +6 -2
  16. package/dist/conformance/fixtures.js.map +1 -1
  17. package/dist/editor/dev.d.ts +7 -0
  18. package/dist/editor/dev.d.ts.map +1 -0
  19. package/dist/editor/dev.js +9 -0
  20. package/dist/editor/dev.js.map +1 -0
  21. package/dist/editor/editor.d.ts +120 -3
  22. package/dist/editor/editor.d.ts.map +1 -1
  23. package/dist/editor/editor.js +133 -27
  24. package/dist/editor/editor.js.map +1 -1
  25. package/dist/features/block-dnd.d.ts.map +1 -1
  26. package/dist/features/block-dnd.js +4 -1
  27. package/dist/features/block-dnd.js.map +1 -1
  28. package/dist/features/code-block.d.ts.map +1 -1
  29. package/dist/features/code-block.js +17 -12
  30. package/dist/features/code-block.js.map +1 -1
  31. package/dist/features/image.d.ts.map +1 -1
  32. package/dist/features/image.js +3 -1
  33. package/dist/features/image.js.map +1 -1
  34. package/dist/features/markdown.d.ts.map +1 -1
  35. package/dist/features/markdown.js +4 -9
  36. package/dist/features/markdown.js.map +1 -1
  37. package/dist/features/search.d.ts.map +1 -1
  38. package/dist/features/search.js +9 -19
  39. package/dist/features/search.js.map +1 -1
  40. package/dist/features/table.d.ts.map +1 -1
  41. package/dist/features/table.js +3 -1
  42. package/dist/features/table.js.map +1 -1
  43. package/dist/history.d.ts.map +1 -1
  44. package/dist/history.js +3 -0
  45. package/dist/history.js.map +1 -1
  46. package/dist/index.d.ts +10 -8
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +6 -6
  49. package/dist/index.js.map +1 -1
  50. package/dist/model/node.d.ts +14 -0
  51. package/dist/model/node.d.ts.map +1 -1
  52. package/dist/model/node.js +67 -0
  53. package/dist/model/node.js.map +1 -1
  54. package/dist/model/schema.d.ts +8 -1
  55. package/dist/model/schema.d.ts.map +1 -1
  56. package/dist/model/schema.js +29 -0
  57. package/dist/model/schema.js.map +1 -1
  58. package/dist/model/traverse.d.ts +4 -0
  59. package/dist/model/traverse.d.ts.map +1 -1
  60. package/dist/model/traverse.js +10 -0
  61. package/dist/model/traverse.js.map +1 -1
  62. package/dist/presets/starter.d.ts +13 -0
  63. package/dist/presets/starter.d.ts.map +1 -1
  64. package/dist/presets/starter.js +9 -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 +42 -11
  68. package/dist/state/apply.js.map +1 -1
  69. package/dist/state/resolve-insert.d.ts.map +1 -1
  70. package/dist/state/resolve-insert.js +5 -2
  71. package/dist/state/resolve-insert.js.map +1 -1
  72. package/dist/state/slice-ops.js +16 -9
  73. package/dist/state/slice-ops.js.map +1 -1
  74. package/dist/state/transaction.d.ts +18 -1
  75. package/dist/state/transaction.d.ts.map +1 -1
  76. package/dist/state/transaction.js +27 -2
  77. package/dist/state/transaction.js.map +1 -1
  78. package/dist/styles.css +316 -84
  79. package/dist/theme.d.ts.map +1 -1
  80. package/dist/theme.js +10 -0
  81. package/dist/theme.js.map +1 -1
  82. package/dist/view/highlight-registry.d.ts +22 -0
  83. package/dist/view/highlight-registry.d.ts.map +1 -0
  84. package/dist/view/highlight-registry.js +73 -0
  85. package/dist/view/highlight-registry.js.map +1 -0
  86. package/dist/view/ime.d.ts.map +1 -1
  87. package/dist/view/ime.js +37 -3
  88. package/dist/view/ime.js.map +1 -1
  89. package/dist/view/input.d.ts.map +1 -1
  90. package/dist/view/input.js +21 -3
  91. package/dist/view/input.js.map +1 -1
  92. package/dist/view/mount.d.ts +5 -0
  93. package/dist/view/mount.d.ts.map +1 -1
  94. package/dist/view/mount.js +19 -1
  95. package/dist/view/mount.js.map +1 -1
  96. package/dist/view/node-selection.d.ts.map +1 -1
  97. package/dist/view/node-selection.js +9 -2
  98. package/dist/view/node-selection.js.map +1 -1
  99. package/package.json +1 -1
package/dist/styles.css CHANGED
@@ -1,11 +1,34 @@
1
1
  /* @moda-editor/core 공통 스타일 — 어댑터는 @import로 재사용한다.
2
2
  * 색상은 --me-* CSS 변수로 노출해 테마를 연다. */
3
3
 
4
+ /* 스코프 안의 모든 요소를 border-box로 정규화 — 임베딩 페이지의
5
+ * 전역 box-sizing과 무관하게 같은 레이아웃이 나온다 */
6
+ .me-root,
7
+ .me-root *,
8
+ .me-root *::before,
9
+ .me-root *::after,
10
+ .me-frame,
11
+ .me-frame *,
12
+ .me-frame *::before,
13
+ .me-frame *::after,
14
+ .me-pop,
15
+ .me-pop *,
16
+ .me-bubble,
17
+ .me-bubble *,
18
+ .me-linkcard,
19
+ .me-linkcard *,
20
+ .me-menu,
21
+ .me-menu * {
22
+ box-sizing: border-box;
23
+ }
24
+
4
25
  .me-root {
5
26
  --me-border-color: #e2e8f0;
6
27
  --me-text-color: #1e293b;
7
28
  --me-font-mono:
8
29
  ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
30
+ /* 본문 폰트 — 기본은 에디터 크롬과 같은 mono, 제품 산문 폰트로 교체 가능 */
31
+ --me-font-body: var(--me-font-mono);
9
32
  /* 거터·활성줄 변수는 루트에 둔다 — .me-gutter 오버레이가 .me-editor의
10
33
  * 형제라 .me-editor 스코프 변수를 못 본다. 자식인 .me-editor도
11
34
  * 같은 변수를 상속해 그라디언트에 쓴다 */
@@ -14,6 +37,25 @@
14
37
  --me-line-active-num: #3b82f6;
15
38
  /* atom 노드 셀렉션 링 + 이미지 핸들 — 거터와 같은 이유로 루트에 둔다 */
16
39
  --me-node-sel-color: #3b82f6;
40
+ /* ── 레이아웃/톤 토큰 — 매직값 대신 변수로 둬 제품이 조정한다 */
41
+ --me-gutter-w: 3.9em;
42
+ --me-code-gutter-w: 3.4em;
43
+ --me-editor-min-height: 160px;
44
+ --me-radius-sm: 4px;
45
+ --me-radius-md: 6px;
46
+ --me-radius-pop: 8px;
47
+ --me-radius-frame: 10px;
48
+ /* 겹침 순서 — 툴바 < 원격 캐럿 < 편집 오버레이 < 버블 < 팝/메뉴 */
49
+ --me-z-toolbar: 20;
50
+ --me-z-carets: 35;
51
+ --me-z-overlay: 40;
52
+ --me-z-float: 50;
53
+ --me-z-pop: 60;
54
+ /* 위험 액션 호버(삭제 버튼)·액센트 위 텍스트 — 테마가 덮는다 */
55
+ --me-danger: #dc2626;
56
+ --me-on-accent: #ffffff;
57
+ /* RTL — 거터 그라디언트 방향만 물리 키워드라 :dir(rtl)에서 뒤집는다 */
58
+ --me-grad-dir: to right;
17
59
  display: flex;
18
60
  flex-direction: column;
19
61
  gap: 8px;
@@ -22,6 +64,13 @@
22
64
  font: inherit;
23
65
  }
24
66
 
67
+ /* RTL — 거터 그라디언트와 같은 물리 방향 키워드를 뒤집는다 */
68
+ :dir(rtl).me-root,
69
+ :dir(rtl) .me-root,
70
+ .me-root :dir(rtl) {
71
+ --me-grad-dir: to left;
72
+ }
73
+
25
74
  .me-search {
26
75
  padding: 6px 10px;
27
76
  border: 1px solid var(--me-border-color);
@@ -71,13 +120,22 @@
71
120
  --me-tool-active-bg: #dbeafe;
72
121
  --me-tool-active-color: #1d4ed8;
73
122
  --me-status-color: #64748b;
123
+ /* 크롬 폴백 — 리프 요소의 var() 폴백이 아니라 여기가 기본값의 고향
124
+ * (--me-editor-focus는 본문과 포커스 링이 공유하는 액센트다) */
125
+ --me-editor-focus: #3b82f6;
126
+ --me-accent: #2563eb;
127
+ --me-on-accent: #ffffff;
128
+ --me-danger: #dc2626;
129
+ --me-text-muted: #64748b;
130
+ --me-btn-bg: #f1f5f9;
131
+ --me-btn-hover-bg: #e2e8f0;
74
132
  /* 뜨는 UI(팝오버/버블/카드/그리드)의 공통 그림자 */
75
133
  --me-pop-shadow: 0 8px 24px rgba(15, 23, 42, 0.18);
76
134
 
77
135
  color: var(--me-text-color);
78
136
  background: var(--me-frame-bg);
79
137
  border: 1px solid var(--me-border-color);
80
- border-radius: 10px;
138
+ border-radius: var(--me-radius-frame, 10px);
81
139
  box-shadow: var(--me-frame-shadow);
82
140
  overflow: hidden;
83
141
  /* 우하단 앵커 요소(상태바 탭·출력 패널)의 containing block */
@@ -123,14 +181,15 @@
123
181
  --me-tok-tag: #116329;
124
182
  --me-tok-attr: #953800;
125
183
 
126
- min-height: 160px;
184
+ min-height: var(--me-editor-min-height, 160px);
127
185
  position: relative;
128
- padding: 10px 16px 10px 3.9em;
186
+ padding-block: 10px;
187
+ padding-inline: var(--me-gutter-w, 3.9em) 16px;
129
188
  border: 1px solid var(--me-border-color);
130
- border-radius: 10px;
189
+ border-radius: var(--me-radius-frame, 10px);
131
190
  color: var(--me-text-color, #1e293b);
132
191
  caret-color: var(--me-editor-focus);
133
- font-family: var(--me-font-mono);
192
+ font-family: var(--me-font-body, var(--me-font-mono));
134
193
  font-size: 13.5px;
135
194
  outline: none;
136
195
  white-space: pre-wrap;
@@ -138,20 +197,20 @@
138
197
  background:
139
198
  /* 거터 구분선 */
140
199
  linear-gradient(
141
- to right,
142
- transparent 0 calc(3.9em - 1px),
143
- var(--me-gutter-border) calc(3.9em - 1px) 3.9em,
144
- transparent 3.9em
200
+ var(--me-grad-dir, to right),
201
+ transparent 0 calc(var(--me-gutter-w, 3.9em) - 1px),
202
+ var(--me-gutter-border) calc(var(--me-gutter-w, 3.9em) - 1px) var(--me-gutter-w, 3.9em),
203
+ transparent var(--me-gutter-w, 3.9em)
145
204
  ),
146
205
  /* print margin — 80열 (Ace showPrintMargin 관례) */
147
206
  linear-gradient(
148
- to right,
149
- transparent 0 calc(3.9em + 80ch),
150
- var(--me-print-margin) calc(3.9em + 80ch) calc(3.9em + 80ch + 1px),
151
- transparent calc(3.9em + 80ch + 1px)
207
+ var(--me-grad-dir, to right),
208
+ transparent 0 calc(var(--me-gutter-w, 3.9em) + 80ch),
209
+ var(--me-print-margin) calc(var(--me-gutter-w, 3.9em) + 80ch) calc(var(--me-gutter-w, 3.9em) + 80ch + 1px),
210
+ transparent calc(var(--me-gutter-w, 3.9em) + 80ch + 1px)
152
211
  ),
153
212
  /* 거터 컬럼 배경 */
154
- linear-gradient(to right, var(--me-gutter-bg) 0 3.9em, transparent 3.9em),
213
+ linear-gradient(var(--me-grad-dir, to right), var(--me-gutter-bg) 0 var(--me-gutter-w, 3.9em), transparent var(--me-gutter-w, 3.9em)),
155
214
  var(--me-editor-bg);
156
215
  }
157
216
 
@@ -165,10 +224,10 @@
165
224
  * 취급)도 같은 이유로 금지 */
166
225
  .me-gutter {
167
226
  position: absolute;
168
- left: 0;
227
+ inset-inline-start: 0;
169
228
  top: 0;
170
229
  bottom: 0;
171
- width: 3.9em;
230
+ width: var(--me-gutter-w, 3.9em);
172
231
  pointer-events: none;
173
232
  }
174
233
  /* user-select:none은 Chrome이 선택 불가 요소를 편집 경계로 취급해
@@ -176,23 +235,23 @@
176
235
  * pointer-events:none+editable 밖이라 선택될 경로도 없다 */
177
236
  .me-gnum {
178
237
  position: absolute;
179
- width: 3.9em;
180
- padding-right: 0.9em;
238
+ width: var(--me-gutter-w, 3.9em);
239
+ padding-inline-end: 0.9em;
181
240
  box-sizing: border-box;
182
- text-align: right;
241
+ text-align: end;
183
242
  color: var(--me-gutter-color);
184
243
  font-family: var(--me-font-mono);
185
244
  font-size: 12px;
186
245
  }
187
246
  .me-gnum.me-gcode {
188
- width: 3.4em;
189
- padding-right: 0.7em;
247
+ width: var(--me-code-gutter-w, 3.4em);
248
+ padding-inline-end: 0.7em;
190
249
  opacity: 0.65;
191
250
  }
192
251
  /* 언어 라벨은 pre 첫 줄의 우측 패딩 자리에 놓인다 — 텍스트와
193
252
  * 겹치지 않는 게 Home/End 안전 조건이라 첫 줄에 여백을 만든다 */
194
253
  .me-editor pre[data-language] .me-cl:first-child {
195
- padding-right: 5em;
254
+ padding-inline-end: 5em;
196
255
  }
197
256
  .me-gnum.is-active {
198
257
  color: var(--me-line-active-num);
@@ -299,7 +358,7 @@
299
358
  * 그려지므로, 마커가 거터 구분선에 닿지 않을 만큼의 들여쓰기가 필요 */
300
359
  .me-editor ul,
301
360
  .me-editor ol {
302
- padding-left: 2.1em;
361
+ padding-inline-start: 2.1em;
303
362
  }
304
363
  .me-editor li > p {
305
364
  margin: 0;
@@ -312,22 +371,22 @@
312
371
  /* 할일 목록 — 마커는 ::before, 클릭 영역은 li의 왼쪽 패딩(~24px).
313
372
  * ul 패딩과 li 패딩을 더해 텍스트 시작점이 다른 리스트와 맞는다 */
314
373
  .me-editor ul.me-tasks {
315
- padding-left: 8px;
374
+ padding-inline-start: 8px;
316
375
  }
317
376
  .me-editor li.me-task {
318
377
  list-style: none;
319
378
  position: relative;
320
- padding-left: 24px;
379
+ padding-inline-start: 24px;
321
380
  }
322
381
  .me-editor li.me-task::before {
323
382
  content: "";
324
383
  position: absolute;
325
- left: 4px;
384
+ inset-inline-start: 4px;
326
385
  top: 0.28em;
327
386
  width: 15px;
328
387
  height: 15px;
329
388
  border: 1.5px solid var(--me-checkbox-border);
330
- border-radius: 4px;
389
+ border-radius: var(--me-radius-sm, 4px);
331
390
  background: var(--me-editor-bg, #fff);
332
391
  cursor: pointer;
333
392
  }
@@ -338,11 +397,11 @@
338
397
  .me-editor li.me-task[data-checked="true"]::after {
339
398
  content: "";
340
399
  position: absolute;
341
- left: 8.5px;
400
+ inset-inline-start: 8.5px;
342
401
  top: 0.42em;
343
402
  width: 4px;
344
403
  height: 8px;
345
- border: solid #fff;
404
+ border: solid var(--me-on-accent, #fff);
346
405
  border-width: 0 2px 2px 0;
347
406
  transform: rotate(45deg);
348
407
  pointer-events: none;
@@ -400,7 +459,7 @@
400
459
  * 통과하고 핸들/바만 이벤트를 받는다 */
401
460
  .me-imgui {
402
461
  position: absolute;
403
- z-index: 40;
462
+ z-index: var(--me-z-overlay, 40);
404
463
  pointer-events: none;
405
464
  }
406
465
  .me-imgui[hidden] {
@@ -409,7 +468,7 @@
409
468
  /* 우하단 크기 핸들 — nwse-resize (GDocs/Word 관례) */
410
469
  .me-imgui .me-img-handle {
411
470
  position: absolute;
412
- right: -6px;
471
+ inset-inline-end: -6px;
413
472
  bottom: -6px;
414
473
  width: 12px;
415
474
  height: 12px;
@@ -424,7 +483,7 @@
424
483
  /* 액션 바 — 이미지 위쪽. 루트 상단에 붙으면 is-below로 아래로 뒤집는다 */
425
484
  .me-imgui .me-img-bar {
426
485
  position: absolute;
427
- left: 0;
486
+ inset-inline-start: 0;
428
487
  top: -6px;
429
488
  transform: translateY(-100%);
430
489
  display: flex;
@@ -459,8 +518,8 @@
459
518
  background: var(--me-tool-hover-bg, #e2e8f0);
460
519
  }
461
520
  .me-imgui .me-imgbtn[data-img-act="del"]:hover {
462
- color: #dc2626;
463
- border-color: #dc2626;
521
+ color: var(--me-danger, #dc2626);
522
+ border-color: var(--me-danger, #dc2626);
464
523
  }
465
524
  /* 편집 폼 — 액션 바 안의 두 번째 행 (wrap으로 전체 폭을 차지).
466
525
  * 최소 폭을 둬 버튼 행보다 바가 좁아져 입력이 찌그러지지 않게 한다 */
@@ -501,7 +560,7 @@
501
560
  * .me-imgui와 같은 패턴: 래퍼 pointer-events:none, 바/핸들만 받는다 */
502
561
  .me-tableui {
503
562
  position: absolute;
504
- z-index: 40;
563
+ z-index: var(--me-z-overlay, 40);
505
564
  pointer-events: none;
506
565
  }
507
566
  .me-tableui[hidden] {
@@ -543,8 +602,8 @@
543
602
  .me-tableui .me-tblbtn[data-tbl-act="tabledel"]:hover,
544
603
  .me-tableui .me-tblbtn[data-tbl-act="rowdel"]:hover,
545
604
  .me-tableui .me-tblbtn[data-tbl-act="coldel"]:hover {
546
- color: #dc2626;
547
- border-color: #dc2626;
605
+ color: var(--me-danger, #dc2626);
606
+ border-color: var(--me-danger, #dc2626);
548
607
  }
549
608
  /* 열 경계 핸들 — 각 열 우측 경계의 8px 스트립. 평소엔 투명하고
550
609
  * hover/드래그 시 리사이즈 커서 + 강조선 (GDocs 열 리사이즈 관례) */
@@ -565,7 +624,7 @@
565
624
  position: absolute;
566
625
  top: 0;
567
626
  bottom: 0;
568
- left: 3px;
627
+ inset-inline-start: 3px;
569
628
  width: 2px;
570
629
  background: transparent;
571
630
  transition: background 0.1s;
@@ -579,7 +638,7 @@
579
638
  * Notion 코드블록 언어 피커 관례. 래퍼는 좌표만 잡고 자식이 이벤트를 받는다 */
580
639
  .me-codeui {
581
640
  position: absolute;
582
- z-index: 40;
641
+ z-index: var(--me-z-overlay, 40);
583
642
  transform: translateX(-100%);
584
643
  display: flex;
585
644
  align-items: center;
@@ -652,7 +711,7 @@
652
711
  }
653
712
  /* Notion 관례 — 언어 표시는 좌측, 액션 버튼은 우측 */
654
713
  .me-codehead .me-codelang {
655
- margin-right: auto;
714
+ margin-inline-end: auto;
656
715
  }
657
716
  /* 카드 안 pre — 카드가 배경/테두리를 담당하므로 pre 자체 크롬은 뗀다 */
658
717
  .me-codeview > .me-node-content {
@@ -675,8 +734,9 @@
675
734
 
676
735
  /* 인용구 */
677
736
  .me-editor blockquote {
678
- padding: 2px 0 2px 12px;
679
- border-left: 3px solid var(--me-border-color);
737
+ padding-block: 2px;
738
+ padding-inline: 12px 0;
739
+ border-inline-start: 3px solid var(--me-border-color);
680
740
  color: var(--me-quote-color);
681
741
  }
682
742
  .me-editor blockquote > :last-child {
@@ -732,24 +792,24 @@
732
792
  * .me-cl이 있는 pre에서만 발동한다 */
733
793
  .me-editor pre:has(.me-cl) {
734
794
  position: relative;
735
- padding-left: 3.4em;
795
+ padding-inline-start: var(--me-code-gutter-w, 3.4em);
736
796
  background:
737
797
  /* 거터 구분선 */
738
798
  linear-gradient(
739
- to right,
740
- transparent 0 calc(3.4em - 1px),
741
- var(--me-code-border) calc(3.4em - 1px) 3.4em,
742
- transparent 3.4em
799
+ var(--me-grad-dir, to right),
800
+ transparent 0 calc(var(--me-code-gutter-w, 3.4em) - 1px),
801
+ var(--me-code-border) calc(var(--me-code-gutter-w, 3.4em) - 1px) var(--me-code-gutter-w, 3.4em),
802
+ transparent var(--me-code-gutter-w, 3.4em)
743
803
  ),
744
804
  /* print margin — 80열 (Ace showPrintMargin 관례) */
745
805
  linear-gradient(
746
- to right,
747
- transparent 0 calc(3.4em + 80ch),
748
- var(--me-print-margin) calc(3.4em + 80ch) calc(3.4em + 80ch + 1px),
749
- transparent calc(3.4em + 80ch + 1px)
806
+ var(--me-grad-dir, to right),
807
+ transparent 0 calc(var(--me-code-gutter-w, 3.4em) + 80ch),
808
+ var(--me-print-margin) calc(var(--me-code-gutter-w, 3.4em) + 80ch) calc(var(--me-code-gutter-w, 3.4em) + 80ch + 1px),
809
+ transparent calc(var(--me-code-gutter-w, 3.4em) + 80ch + 1px)
750
810
  ),
751
811
  /* 거터 컬럼 배경 */
752
- linear-gradient(to right, var(--me-gutter-bg) 0 3.4em, transparent 3.4em),
812
+ linear-gradient(var(--me-grad-dir, to right), var(--me-gutter-bg) 0 var(--me-code-gutter-w, 3.4em), transparent var(--me-code-gutter-w, 3.4em)),
753
813
  var(--me-code-bg);
754
814
  }
755
815
  /* 활성 줄 — 바인딩의 restoreSelection이 DOM에 is-active를 단다
@@ -832,11 +892,20 @@
832
892
  text-underline-offset: 2px;
833
893
  }
834
894
 
835
- /* 플레이스홀더 — 문서가 빈 문단 하나뿐일 때만 표시 (PM 관례) */
895
+ /* 플레이스홀더 — 문서가 빈 문단 하나뿐일 때만 표시 (PM 관례).
896
+ * data-me-placeholder 속성(EditorOptions.placeholder)이 우선,
897
+ * 없으면 --me-placeholder-text 변수, 최종 기본은 한국어 문구 */
898
+ .me-editor[data-me-placeholder] > p:only-child:has(> br:only-child)::before,
899
+ .me-editor[data-me-placeholder] > p:only-child:has(> span > br:only-child)::before {
900
+ content: attr(data-me-placeholder);
901
+ }
902
+ .me-editor:not([data-me-placeholder]) > p:only-child:has(> br:only-child)::before,
903
+ .me-editor:not([data-me-placeholder]) > p:only-child:has(> span > br:only-child)::before {
904
+ content: var(--me-placeholder-text, "내용을 입력하세요…");
905
+ }
836
906
  .me-editor > p:only-child:has(> br:only-child)::before,
837
907
  .me-editor > p:only-child:has(> span > br:only-child)::before {
838
- content: var(--me-placeholder-text, "내용을 입력하세요…");
839
- float: left;
908
+ float: inline-start;
840
909
  height: 0;
841
910
  color: var(--me-placeholder-color);
842
911
  pointer-events: none;
@@ -844,8 +913,8 @@
844
913
 
845
914
  /* 선택 버블 메뉴 — 텍스트 선택 시 떠오르는 인라인 서식 바 */
846
915
  .me-bubble {
847
- position: fixed;
848
- z-index: 50;
916
+ position: absolute;
917
+ z-index: var(--me-z-float, 50);
849
918
  display: flex;
850
919
  align-items: center;
851
920
  gap: 2px;
@@ -865,8 +934,8 @@
865
934
  * 크롬). 트리거 버튼 아래에 앵커로 뜨고, 좁은 뷰포트에서는 접혀서
866
935
  * 넘치지 않는다 (flex-wrap + max-width) */
867
936
  .me-pop {
868
- position: fixed;
869
- z-index: 60;
937
+ position: absolute;
938
+ z-index: var(--me-z-pop, 60);
870
939
  display: flex;
871
940
  align-items: center;
872
941
  flex-wrap: wrap;
@@ -904,7 +973,7 @@
904
973
  background: var(--me-btn-hover-bg, #e2e8f0);
905
974
  }
906
975
  .me-pop-btn[data-pop-act="ok"] {
907
- color: #fff;
976
+ color: var(--me-on-accent, #fff);
908
977
  background: var(--me-accent, #2563eb);
909
978
  border-color: var(--me-accent, #2563eb);
910
979
  }
@@ -938,14 +1007,14 @@
938
1007
  .me-pop-count {
939
1008
  min-width: 34px;
940
1009
  font-size: 11px;
941
- color: var(--me-muted-color, #64748b);
1010
+ color: var(--me-text-muted, #64748b);
942
1011
  text-align: center;
943
1012
  }
944
1013
  /* 보기 전환 탭 — 상태바 우측의 에디터/마크다운 세그먼트
945
1014
  * (VS Code editor|preview 관례). .me-frame의 data-me-view를 바꾼다 */
946
1015
  .me-viewtabs {
947
1016
  display: inline-flex;
948
- margin-left: 8px;
1017
+ margin-inline-start: 8px;
949
1018
  border: 1px solid var(--me-border-color);
950
1019
  border-radius: 5px;
951
1020
  overflow: hidden;
@@ -959,7 +1028,7 @@
959
1028
  cursor: pointer;
960
1029
  }
961
1030
  .me-viewtabs button + button {
962
- border-left: 1px solid var(--me-border-color);
1031
+ border-inline-start: 1px solid var(--me-border-color);
963
1032
  }
964
1033
  .me-viewtabs button:hover {
965
1034
  background: var(--me-btn-hover-bg, #e2e8f0);
@@ -1007,6 +1076,11 @@
1007
1076
  .me-md-view .me-md-text:focus {
1008
1077
  outline: none;
1009
1078
  }
1079
+ /* 키보드 포커스엔 대체 링 — outline:none 단독은 focus-visible을 지운다 */
1080
+ .me-md-view .me-md-text:focus-visible {
1081
+ outline: 2px solid var(--me-editor-focus, #3b82f6);
1082
+ outline-offset: -2px;
1083
+ }
1010
1084
  .me-pop-textarea {
1011
1085
  width: 100%;
1012
1086
  height: 160px;
@@ -1056,8 +1130,8 @@
1056
1130
  /* 링크 카드 — 캐럿이 링크 위에 있을 때 뜨는 액션 칩 (GDocs 관례):
1057
1131
  * URL 표시 + 복사/수정/제거. 수정은 링크 팝오버로 이어진다 */
1058
1132
  .me-linkcard {
1059
- position: fixed;
1060
- z-index: 60;
1133
+ position: absolute;
1134
+ z-index: var(--me-z-pop, 60);
1061
1135
  display: flex;
1062
1136
  align-items: center;
1063
1137
  gap: 4px;
@@ -1111,7 +1185,7 @@
1111
1185
  padding: 8px 12px;
1112
1186
  background: var(--me-callout-bg);
1113
1187
  border: 1px solid var(--me-callout-border);
1114
- border-left: 3px solid var(--me-callout-accent);
1188
+ border-inline-start: 3px solid var(--me-callout-accent);
1115
1189
  border-radius: 6px;
1116
1190
  }
1117
1191
  .me-callout-icon {
@@ -1139,7 +1213,7 @@
1139
1213
  * 올려 ⋯/A▾ 드롭다운이 본문에 가려지지 않게 한다. 팝오버/버블
1140
1214
  * (fixed, z 50~60)보다는 아래라 겹쳐도 그쪽이 위에 뜬다 */
1141
1215
  position: relative;
1142
- z-index: 20;
1216
+ z-index: var(--me-z-toolbar, 20);
1143
1217
  gap: 2px;
1144
1218
  padding: 6px 44px 6px 8px;
1145
1219
  background: var(--me-toolbar-bg, #f8fafc);
@@ -1243,7 +1317,7 @@
1243
1317
  position: absolute;
1244
1318
  /* translateY 중앙정렬 대신 패딩과 같은 top — transform은 스태킹
1245
1319
  * 컨텍스트를 만들어 자식 .me-menu의 z-index를 가둔다 */
1246
- right: 8px;
1320
+ inset-inline-end: 8px;
1247
1321
  top: 6px;
1248
1322
  flex-shrink: 0;
1249
1323
  background: var(--me-toolbar-bg, #f8fafc);
@@ -1292,9 +1366,9 @@
1292
1366
  .me-menu {
1293
1367
  position: absolute;
1294
1368
  top: calc(100% + 6px);
1295
- right: 0;
1369
+ inset-inline-end: 0;
1296
1370
  left: auto;
1297
- z-index: 60;
1371
+ z-index: var(--me-z-pop, 60);
1298
1372
  display: flex;
1299
1373
  align-items: center;
1300
1374
  gap: 2px;
@@ -1307,8 +1381,8 @@
1307
1381
  }
1308
1382
  /* A▾ 패널은 툴바 중간에서 열리므로 버튼 왼쪽 아래에 붙인다 */
1309
1383
  .me-styles .me-menu {
1310
- right: auto;
1311
- left: 0;
1384
+ inset-inline-end: auto;
1385
+ inset-inline-start: 0;
1312
1386
  }
1313
1387
 
1314
1388
  /* ── 좁은 폭 — 툴바는 flex-wrap으로 여러 줄로 감긴다 ───────────
@@ -1441,7 +1515,7 @@
1441
1515
  );
1442
1516
  }
1443
1517
  .me-pop-theme .me-theme-check {
1444
- margin-left: auto;
1518
+ margin-inline-start: auto;
1445
1519
  font-weight: 600;
1446
1520
  }
1447
1521
 
@@ -1459,7 +1533,7 @@
1459
1533
  * (3.9em) 안에 두고 핸들만 이벤트를 받는다 */
1460
1534
  .me-drag-handle {
1461
1535
  position: absolute;
1462
- left: 0.2em;
1536
+ inset-inline-start: 0.2em;
1463
1537
  width: 1.4em;
1464
1538
  height: 1.4em;
1465
1539
  display: none;
@@ -1486,8 +1560,8 @@
1486
1560
  /* 드롭 경계 표시선 — dragover 동안 삽입 위치에 그린다 */
1487
1561
  .me-drop-line {
1488
1562
  position: absolute;
1489
- left: 3.9em;
1490
- right: 0;
1563
+ inset-inline-start: var(--me-gutter-w, 3.9em);
1564
+ inset-inline-end: 0;
1491
1565
  height: 2px;
1492
1566
  display: none;
1493
1567
  background: var(--me-node-sel-color);
@@ -1513,7 +1587,7 @@
1513
1587
  position: absolute;
1514
1588
  inset: 0;
1515
1589
  pointer-events: none;
1516
- z-index: 35;
1590
+ z-index: var(--me-z-carets, 35);
1517
1591
  overflow: hidden;
1518
1592
  }
1519
1593
  /* 캐럿 바 — 2px 세로선, 높이는 JS가 줄 높이로 맞춘다 */
@@ -1526,11 +1600,11 @@
1526
1600
  .me-remote-caret-label {
1527
1601
  position: absolute;
1528
1602
  top: -1.15em;
1529
- left: -1px;
1603
+ inset-inline-start: -1px;
1530
1604
  padding: 0 4px;
1531
1605
  border-radius: 3px 3px 3px 0;
1532
1606
  background: var(--me-remote-color, #f59e0b);
1533
- color: #fff;
1607
+ color: var(--me-on-accent, #fff);
1534
1608
  font:
1535
1609
  600 10px/1.5 ui-sans-serif,
1536
1610
  system-ui,
@@ -1541,9 +1615,167 @@
1541
1615
  /* 원격 선택 영역 — Range.getClientRects()로 줄별 rect를 깐다 */
1542
1616
  .me-remote-sel {
1543
1617
  position: absolute;
1544
- background: color-mix(
1545
- in srgb,
1546
- var(--me-remote-color, #f59e0b) 22%,
1547
- transparent
1548
- );
1618
+ /* color-mix 미지원 폴백 — 불투명 색 위에 요소 투명도 */
1619
+ background: var(--me-remote-color, #f59e0b);
1620
+ opacity: 0.22;
1621
+ }
1622
+ @supports (background: color-mix(in srgb, red, blue)) {
1623
+ .me-remote-sel {
1624
+ background: color-mix(
1625
+ in srgb,
1626
+ var(--me-remote-color, #f59e0b) 22%,
1627
+ transparent
1628
+ );
1629
+ opacity: 1;
1630
+ }
1631
+ }
1632
+
1633
+ /* ── 접근성 미디어 — 모션 최소화 + 인쇄 + OS 다크 선호 ────────── */
1634
+
1635
+ /* prefers-reduced-motion — 오버레이/핸들의 transition을 끈다 */
1636
+ @media (prefers-reduced-motion: reduce) {
1637
+ .me-root *,
1638
+ .me-frame * {
1639
+ transition-duration: 0.01ms !important;
1640
+ animation-duration: 0.01ms !important;
1641
+ }
1642
+ }
1643
+
1644
+ /* 인쇄 — 편집 크롬(툴바/상태바/팝/오버레이)을 빼고 본문만 남긴다 */
1645
+ @media print {
1646
+ .me-toolbar,
1647
+ .me-statusbar,
1648
+ .me-pop,
1649
+ .me-bubble,
1650
+ .me-menu,
1651
+ .me-linkcard,
1652
+ .me-imgui,
1653
+ .me-tableui,
1654
+ .me-codeui,
1655
+ .me-dnd,
1656
+ .me-carets,
1657
+ .me-gutter,
1658
+ .me-md-head {
1659
+ display: none !important;
1660
+ }
1661
+ .me-frame,
1662
+ .me-editor {
1663
+ border: none;
1664
+ box-shadow: none;
1665
+ }
1666
+ /* 거터가 사라지므로 본문 패딩을 되돌린다 */
1667
+ .me-editor {
1668
+ padding-inline-start: 16px;
1669
+ background: var(--me-editor-bg);
1670
+ }
1671
+ }
1672
+
1673
+ /* OS 다크 선호 — 명시 테마([data-me-theme])가 없는 기본 팔레트만
1674
+ * 어둡게 전환한다. applyTheme이 박은 인라인 변수는 더 높은 우선순위라
1675
+ * 테마가 켜진 에디터는 영향받지 않는다 */
1676
+ @media (prefers-color-scheme: dark) {
1677
+ :where(.me-root, .me-frame, .me-editor):not([data-me-theme]):not([data-me-theme] *) {
1678
+ --me-frame-bg: #1e1f1c;
1679
+ --me-border-color: #3e3d32;
1680
+ --me-text-color: #f8f8f2;
1681
+ --me-editor-bg: #272822;
1682
+ --me-editor-focus: #66d9ef;
1683
+ --me-sel-bg: rgba(73, 72, 62, 0.95);
1684
+ --me-gutter-bg: rgba(0, 0, 0, 0.22);
1685
+ --me-gutter-color: #8f908a;
1686
+ --me-gutter-border: #3e3d32;
1687
+ --me-line-active-num: #c2c2bf;
1688
+ --me-node-sel-color: #66d9ef;
1689
+ --me-print-margin: rgba(73, 72, 62, 0.9);
1690
+ --me-toolbar-bg: #1e1f1c;
1691
+ --me-tool-hover-bg: #3e3d32;
1692
+ --me-tool-active-bg: rgba(102, 217, 239, 0.14);
1693
+ --me-tool-active-color: #66d9ef;
1694
+ --me-status-color: #8f908a;
1695
+ --me-btn-bg: #3e3d32;
1696
+ --me-btn-hover-bg: #575643;
1697
+ --me-accent: #66d9ef;
1698
+ --me-on-accent: #1e1f1c;
1699
+ --me-danger: #f87171;
1700
+ --me-text-muted: #8f908a;
1701
+ --me-link-color: #66d9ef;
1702
+ --me-quote-color: #a09f93;
1703
+ --me-placeholder-color: #6a6b60;
1704
+ --me-code-bg: #1e1f1c;
1705
+ --me-code-border: #3e3d32;
1706
+ --me-code-active-bg: rgba(255, 255, 255, 0.05);
1707
+ --me-callout-bg: #2d2e25;
1708
+ --me-callout-border: #575643;
1709
+ --me-callout-accent: #66d9ef;
1710
+ --me-checkbox-border: #575643;
1711
+ --me-checkbox-checked: #a6e22e;
1712
+ --me-pop-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
1713
+ --me-frame-shadow: none;
1714
+ --me-mention-bg: rgba(102, 217, 239, 0.15);
1715
+ --me-mention-color: #66d9ef;
1716
+ --me-find-bg: rgba(250, 204, 21, 0.25);
1717
+ --me-find-active-bg: rgba(249, 115, 22, 0.55);
1718
+ --me-bracket-bg: rgba(102, 217, 239, 0.3);
1719
+ --me-tok-keyword: #f92672;
1720
+ --me-tok-string: #e6db74;
1721
+ --me-tok-comment: #75715e;
1722
+ --me-tok-number: #ae81ff;
1723
+ --me-tok-literal: #ae81ff;
1724
+ --me-tok-function: #a6e22e;
1725
+ --me-tok-tag: #f92672;
1726
+ --me-tok-attr: #a6e22e;
1727
+ color-scheme: dark;
1728
+ }
1729
+ }
1730
+
1731
+ /* ── 포커스 링 — 오버레이/팝/카드 컨트롤의 focus-visible ──────────
1732
+ * 키보드 탐색 시에만 링을 띄운다 (마우스 클릭엔 없음) */
1733
+ .me-pop-btn:focus-visible,
1734
+ .me-imgui .me-imgbtn:focus-visible,
1735
+ .me-tableui .me-tblbtn:focus-visible,
1736
+ .me-codeui .me-codebtn:focus-visible,
1737
+ .me-codeui .me-codelang:focus-visible,
1738
+ .me-codehead .me-codebtn:focus-visible,
1739
+ .me-codehead .me-codelang:focus-visible,
1740
+ .me-viewtabs button:focus-visible,
1741
+ .me-pop-theme:focus-visible,
1742
+ .me-tcell:focus-visible {
1743
+ outline: 2px solid var(--me-editor-focus, #3b82f6);
1744
+ outline-offset: -2px;
1745
+ }
1746
+
1747
+ /* ── 읽기 전용 — [data-me-readonly] 마커가 편집 단서를 억제한다 ── */
1748
+ .me-root[data-me-readonly] .me-editor {
1749
+ caret-color: transparent;
1750
+ cursor: default;
1751
+ }
1752
+ .me-root[data-me-readonly] .me-editor:focus {
1753
+ border-color: var(--me-border-color);
1754
+ }
1755
+ .me-frame:focus-within:has([data-me-readonly]) {
1756
+ border-color: var(--me-border-color);
1757
+ }
1758
+ .me-root[data-me-readonly] li.me-task::before {
1759
+ cursor: default;
1760
+ }
1761
+
1762
+ /* ── 터치 — pointer:coarse에서는 hover 없이도 드래그 핸들이 보이고
1763
+ * 리사이즈/열 핸들의 손가락 타겟을 키운다 (44px 접근성 관례) ── */
1764
+ @media (pointer: coarse) {
1765
+ .me-drag-handle {
1766
+ display: flex;
1767
+ width: 2em;
1768
+ height: 2em;
1769
+ align-items: flex-start;
1770
+ padding-top: 0.15em;
1771
+ }
1772
+ .me-imgui .me-img-handle {
1773
+ width: 20px;
1774
+ height: 20px;
1775
+ inset-inline-end: -10px;
1776
+ bottom: -10px;
1777
+ }
1778
+ .me-tableui .me-col-handle {
1779
+ width: 16px;
1780
+ }
1549
1781
  }