@teriusu/rich-editor 0.4.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 (90) hide show
  1. package/README.md +178 -0
  2. package/dist/components/BubbleToolbar.svelte +299 -0
  3. package/dist/components/BubbleToolbar.svelte.d.ts +9 -0
  4. package/dist/components/BubbleToolbar.svelte.d.ts.map +1 -0
  5. package/dist/components/FixedToolbar.svelte +892 -0
  6. package/dist/components/FixedToolbar.svelte.d.ts +11 -0
  7. package/dist/components/FixedToolbar.svelte.d.ts.map +1 -0
  8. package/dist/components/InputModal.svelte +102 -0
  9. package/dist/components/InputModal.svelte.d.ts +11 -0
  10. package/dist/components/InputModal.svelte.d.ts.map +1 -0
  11. package/dist/components/PdfViewer.svelte +152 -0
  12. package/dist/components/PdfViewer.svelte.d.ts +8 -0
  13. package/dist/components/PdfViewer.svelte.d.ts.map +1 -0
  14. package/dist/components/SlashCommandMenu.svelte +315 -0
  15. package/dist/components/SlashCommandMenu.svelte.d.ts +14 -0
  16. package/dist/components/SlashCommandMenu.svelte.d.ts.map +1 -0
  17. package/dist/components/TableBubbleMenu.svelte +472 -0
  18. package/dist/components/TableBubbleMenu.svelte.d.ts +8 -0
  19. package/dist/components/TableBubbleMenu.svelte.d.ts.map +1 -0
  20. package/dist/components/TipTapEditor.svelte +649 -0
  21. package/dist/components/TipTapEditor.svelte.d.ts +17 -0
  22. package/dist/components/TipTapEditor.svelte.d.ts.map +1 -0
  23. package/dist/extensions/Column.d.ts +3 -0
  24. package/dist/extensions/Column.d.ts.map +1 -0
  25. package/dist/extensions/Column.js +20 -0
  26. package/dist/extensions/Columns.d.ts +11 -0
  27. package/dist/extensions/Columns.d.ts.map +1 -0
  28. package/dist/extensions/Columns.js +103 -0
  29. package/dist/extensions/Columns.test.d.ts +2 -0
  30. package/dist/extensions/Columns.test.d.ts.map +1 -0
  31. package/dist/extensions/Columns.test.js +62 -0
  32. package/dist/extensions/FileAttachment.d.ts +24 -0
  33. package/dist/extensions/FileAttachment.d.ts.map +1 -0
  34. package/dist/extensions/FileAttachment.js +190 -0
  35. package/dist/extensions/FileAttachment.test.d.ts +2 -0
  36. package/dist/extensions/FileAttachment.test.d.ts.map +1 -0
  37. package/dist/extensions/FileAttachment.test.js +63 -0
  38. package/dist/extensions/FixedDetails.d.ts +2 -0
  39. package/dist/extensions/FixedDetails.d.ts.map +1 -0
  40. package/dist/extensions/FixedDetails.js +100 -0
  41. package/dist/extensions/FixedDetails.test.d.ts +2 -0
  42. package/dist/extensions/FixedDetails.test.d.ts.map +1 -0
  43. package/dist/extensions/FixedDetails.test.js +40 -0
  44. package/dist/extensions/Indent.d.ts +11 -0
  45. package/dist/extensions/Indent.d.ts.map +1 -0
  46. package/dist/extensions/Indent.js +87 -0
  47. package/dist/extensions/Indent.test.d.ts +2 -0
  48. package/dist/extensions/Indent.test.d.ts.map +1 -0
  49. package/dist/extensions/Indent.test.js +78 -0
  50. package/dist/extensions/PdfBlock.d.ts +3 -0
  51. package/dist/extensions/PdfBlock.d.ts.map +1 -0
  52. package/dist/extensions/PdfBlock.js +301 -0
  53. package/dist/extensions/PdfBlock.test.d.ts +2 -0
  54. package/dist/extensions/PdfBlock.test.d.ts.map +1 -0
  55. package/dist/extensions/PdfBlock.test.js +51 -0
  56. package/dist/extensions/VideoBlock.d.ts +16 -0
  57. package/dist/extensions/VideoBlock.d.ts.map +1 -0
  58. package/dist/extensions/VideoBlock.js +87 -0
  59. package/dist/extensions/VideoBlock.test.d.ts +2 -0
  60. package/dist/extensions/VideoBlock.test.d.ts.map +1 -0
  61. package/dist/extensions/VideoBlock.test.js +54 -0
  62. package/dist/index.d.ts +20 -0
  63. package/dist/index.d.ts.map +1 -0
  64. package/dist/index.js +20 -0
  65. package/dist/styles/editor.css +698 -0
  66. package/dist/types.d.ts +44 -0
  67. package/dist/types.d.ts.map +1 -0
  68. package/dist/types.js +1 -0
  69. package/dist/utils/cn.d.ts +3 -0
  70. package/dist/utils/cn.d.ts.map +1 -0
  71. package/dist/utils/cn.js +5 -0
  72. package/dist/utils/cn.test.d.ts +2 -0
  73. package/dist/utils/cn.test.d.ts.map +1 -0
  74. package/dist/utils/cn.test.js +28 -0
  75. package/dist/utils/escape-html.d.ts +2 -0
  76. package/dist/utils/escape-html.d.ts.map +1 -0
  77. package/dist/utils/escape-html.js +7 -0
  78. package/dist/utils/escape-html.test.d.ts +2 -0
  79. package/dist/utils/escape-html.test.d.ts.map +1 -0
  80. package/dist/utils/escape-html.test.js +25 -0
  81. package/dist/utils/pdf.d.ts +9 -0
  82. package/dist/utils/pdf.d.ts.map +1 -0
  83. package/dist/utils/pdf.js +33 -0
  84. package/dist/utils/sanitize.d.ts +9 -0
  85. package/dist/utils/sanitize.d.ts.map +1 -0
  86. package/dist/utils/sanitize.js +115 -0
  87. package/dist/utils/sanitize.test.d.ts +2 -0
  88. package/dist/utils/sanitize.test.d.ts.map +1 -0
  89. package/dist/utils/sanitize.test.js +206 -0
  90. package/package.json +76 -0
@@ -0,0 +1,698 @@
1
+ /* @hancomac/editor 스타일 */
2
+
3
+ /* ── 에디터 wrapper — 메뉴가 잘리지 않도록 overflow 보장 ── */
4
+ .hce-editor-wrapper {
5
+ overflow: visible !important;
6
+ }
7
+
8
+ /* ── 기본 레이아웃 (공식 TipTap 패턴) ── */
9
+ .tiptap {
10
+ font-size: 1.0625rem;
11
+ line-height: 1.8;
12
+ color: var(--foreground);
13
+ padding: 1.5rem 1rem 1.5rem 0;
14
+ }
15
+
16
+ .tiptap > * + * {
17
+ margin-top: 0.5em;
18
+ }
19
+
20
+ .tiptap > * {
21
+ margin-left: 1.5rem;
22
+ margin-right: 1.5rem;
23
+ }
24
+
25
+ /* ── 텍스트 블록 ── */
26
+ .tiptap p {
27
+ margin: 0.25em 0;
28
+ }
29
+
30
+ /* 빈 행 높이 보존 */
31
+ .tiptap p:empty,
32
+ .tiptap p:has(> br:only-child) {
33
+ min-height: 1.8em;
34
+ }
35
+
36
+ /* 선행 공백 보존 */
37
+ .tiptap p,
38
+ .tiptap li,
39
+ .tiptap h1, .tiptap h2, .tiptap h3 {
40
+ white-space: pre-wrap;
41
+ }
42
+
43
+ .tiptap h1 {
44
+ font-size: 2rem;
45
+ font-weight: 700;
46
+ margin: 1.5em 0 0.5em;
47
+ line-height: 1.25;
48
+ }
49
+
50
+ .tiptap h2 {
51
+ font-size: 1.5rem;
52
+ font-weight: 600;
53
+ margin: 1.2em 0 0.4em;
54
+ line-height: 1.3;
55
+ }
56
+
57
+ .tiptap h3 {
58
+ font-size: 1.25rem;
59
+ font-weight: 600;
60
+ margin: 1em 0 0.3em;
61
+ line-height: 1.35;
62
+ }
63
+
64
+ /* ── 리스트 ── */
65
+ .tiptap ul {
66
+ list-style: disc;
67
+ padding-left: 1.5em;
68
+ margin: 0.5em 0;
69
+ }
70
+
71
+ .tiptap ol {
72
+ list-style: decimal;
73
+ padding-left: 1.5em;
74
+ margin: 0.5em 0;
75
+ }
76
+
77
+ /* 중첩 리스트 레벨별 불릿 (Notion 스타일) */
78
+ .tiptap ul ul { list-style: circle; }
79
+ .tiptap ul ul ul { list-style: square; }
80
+ .tiptap ol ol { list-style: lower-alpha; }
81
+ .tiptap ol ol ol { list-style: lower-roman; }
82
+
83
+ .tiptap li {
84
+ margin: 0.25em 0;
85
+ }
86
+
87
+ /* ── 체크리스트 (TaskList) ── */
88
+ .tiptap ul[data-type="taskList"] {
89
+ list-style: none;
90
+ padding-left: 0;
91
+ }
92
+
93
+ .tiptap ul[data-type="taskList"] li {
94
+ display: flex;
95
+ align-items: baseline;
96
+ gap: 0.5rem;
97
+ margin: 0.1em 0;
98
+ list-style: none;
99
+ list-style-type: none;
100
+ }
101
+
102
+ .tiptap ul[data-type="taskList"] li > label {
103
+ flex-shrink: 0;
104
+ display: flex;
105
+ align-items: center;
106
+ height: 1.5em;
107
+ }
108
+
109
+ .tiptap ul[data-type="taskList"] li > label input[type="checkbox"] {
110
+ accent-color: var(--primary);
111
+ width: 1.1em;
112
+ height: 1.1em;
113
+ cursor: pointer;
114
+ margin: 0;
115
+ }
116
+
117
+ .tiptap ul[data-type="taskList"] li > div {
118
+ flex: 1;
119
+ }
120
+
121
+ .tiptap ul[data-type="taskList"] li[data-checked="true"] > div {
122
+ text-decoration: line-through;
123
+ opacity: 0.6;
124
+ }
125
+
126
+ /* ── 인용문 ── */
127
+ .tiptap blockquote {
128
+ border-left: 3px solid var(--border);
129
+ padding: 0.5rem 1rem;
130
+ margin: 0.75em 0;
131
+ color: var(--muted-foreground);
132
+ font-style: italic;
133
+ }
134
+
135
+ /* ── 구분선 ── */
136
+ .tiptap hr {
137
+ border: none;
138
+ border-top: 1px solid var(--border);
139
+ margin: 1.5em 0;
140
+ }
141
+
142
+ /* ── 코드 ── */
143
+ .tiptap code {
144
+ background: var(--muted);
145
+ border-radius: 4px;
146
+ padding: 0.15em 0.35em;
147
+ font-size: 0.9em;
148
+ }
149
+
150
+ .tiptap pre {
151
+ background: #282c34;
152
+ color: #abb2bf;
153
+ border-radius: 8px;
154
+ padding: 1em;
155
+ margin: 0.75em 0;
156
+ overflow-x: auto;
157
+ font-family: "JetBrains Mono", "Fira Code", "Consolas", monospace;
158
+ font-size: 0.9em;
159
+ line-height: 1.6;
160
+ }
161
+
162
+ .tiptap pre code {
163
+ background: none;
164
+ padding: 0;
165
+ border-radius: 0;
166
+ color: #abb2bf;
167
+ font-size: inherit;
168
+ }
169
+
170
+ /* Atom One Dark 공식 토큰 색상 (highlight.js/styles/atom-one-dark.css 의존 제거) */
171
+ .tiptap pre code .hljs-keyword,
172
+ .tiptap pre code .hljs-operator { color: #c678dd; }
173
+
174
+ .tiptap pre code .hljs-string,
175
+ .tiptap pre code .hljs-doctag,
176
+ .tiptap pre code .hljs-addition,
177
+ .tiptap pre code .hljs-regexp { color: #98c379; }
178
+
179
+ .tiptap pre code .hljs-number,
180
+ .tiptap pre code .hljs-attr,
181
+ .tiptap pre code .hljs-type,
182
+ .tiptap pre code .hljs-template-variable,
183
+ .tiptap pre code .hljs-variable,
184
+ .tiptap pre code .hljs-selector-class { color: #d19a66; }
185
+
186
+ .tiptap pre code .hljs-title,
187
+ .tiptap pre code .hljs-title.class_,
188
+ .tiptap pre code .hljs-function,
189
+ .tiptap pre code .hljs-section { color: #61aeee; }
190
+
191
+ .tiptap pre code .hljs-params,
192
+ .tiptap pre code .hljs-punctuation { color: #abb2bf; }
193
+
194
+ .tiptap pre code .hljs-comment,
195
+ .tiptap pre code .hljs-quote { color: #5c6370; font-style: italic; }
196
+
197
+ .tiptap pre code .hljs-built_in,
198
+ .tiptap pre code .hljs-formula { color: #e6c07b; }
199
+
200
+ .tiptap pre code .hljs-meta,
201
+ .tiptap pre code .hljs-meta .hljs-keyword { color: #61aeee; }
202
+
203
+ .tiptap pre code .hljs-meta .hljs-string { color: #98c379; }
204
+
205
+ .tiptap pre code .hljs-deletion,
206
+ .tiptap pre code .hljs-name,
207
+ .tiptap pre code .hljs-tag,
208
+ .tiptap pre code .hljs-property,
209
+ .tiptap pre code .hljs-selector-tag,
210
+ .tiptap pre code .hljs-subst,
211
+ .tiptap pre code .hljs-attribute { color: #e06c75; }
212
+
213
+ .tiptap pre code .hljs-literal { color: #56b6c2; }
214
+
215
+ .tiptap pre code .hljs-selector-id,
216
+ .tiptap pre code .hljs-selector-attr,
217
+ .tiptap pre code .hljs-selector-pseudo { color: #61aeee; }
218
+
219
+ .tiptap pre code .hljs-link { color: #61aeee; text-decoration: underline; }
220
+
221
+ .tiptap pre code .hljs-symbol,
222
+ .tiptap pre code .hljs-bullet { color: #61aeee; }
223
+
224
+ .tiptap pre code .hljs-emphasis { font-style: italic; }
225
+ .tiptap pre code .hljs-strong { font-weight: bold; }
226
+
227
+ /* ── 이미지 ── */
228
+ .tiptap img {
229
+ max-width: 100%;
230
+ height: auto;
231
+ border-radius: 8px;
232
+ margin: 0.75em 0;
233
+ cursor: pointer;
234
+ transition: box-shadow 0.15s;
235
+ }
236
+
237
+ .tiptap img.ProseMirror-selectednode {
238
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 25%, transparent);
239
+ }
240
+
241
+ /* ── YouTube / PDF 선택 힌트 ── */
242
+ .tiptap div[data-youtube-video].ProseMirror-selectednode iframe {
243
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 25%, transparent);
244
+ }
245
+
246
+ .tiptap [data-type="pdfBlock"].ProseMirror-selectednode > div {
247
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 25%, transparent);
248
+ border-radius: 8px;
249
+ }
250
+
251
+ /* ── 링크 ── */
252
+ .tiptap a {
253
+ color: var(--primary);
254
+ text-decoration: underline;
255
+ text-underline-offset: 2px;
256
+ }
257
+
258
+ .tiptap a:hover {
259
+ opacity: 0.8;
260
+ }
261
+
262
+ /* ── 하이라이트 ── */
263
+ .tiptap mark {
264
+ background-color: #fef08a;
265
+ border-radius: 2px;
266
+ padding: 0.05em 0.1em;
267
+ }
268
+
269
+ /* ── 접기/펼치기 (Details) ── */
270
+ /* 에디터 내부: TipTap node view (div[data-type="details"]) */
271
+ .tiptap [data-type="details"] {
272
+ border: none;
273
+ border-radius: 0;
274
+ padding: 0.25em 0;
275
+ margin: 0.5em 0;
276
+ background: transparent;
277
+ display: flex;
278
+ align-items: flex-start;
279
+ gap: 0.25em;
280
+ }
281
+
282
+ .tiptap [data-type="details"] > button {
283
+ background: none;
284
+ border: none;
285
+ cursor: pointer;
286
+ padding: 0;
287
+ width: 1.4em;
288
+ height: 1.4em;
289
+ display: inline-flex;
290
+ align-items: center;
291
+ justify-content: center;
292
+ color: var(--muted-foreground);
293
+ border-radius: 4px;
294
+ transition: transform 0.15s, background 0.15s;
295
+ flex-shrink: 0;
296
+ margin-top: 0.25em;
297
+ }
298
+
299
+ .tiptap [data-type="details"] > button::after {
300
+ content: "";
301
+ display: block;
302
+ width: 0;
303
+ height: 0;
304
+ border-left: 5px solid currentColor;
305
+ border-top: 4px solid transparent;
306
+ border-bottom: 4px solid transparent;
307
+ margin-left: 2px;
308
+ }
309
+
310
+ .tiptap [data-type="details"] > button:hover {
311
+ background: var(--muted);
312
+ color: var(--foreground);
313
+ }
314
+
315
+ .tiptap [data-type="details"].is-open > button {
316
+ transform: rotate(90deg);
317
+ }
318
+
319
+ .tiptap [data-type="details"] > div {
320
+ flex: 1;
321
+ min-width: 0;
322
+ }
323
+
324
+ .tiptap [data-type="details"] summary {
325
+ font-weight: 600;
326
+ cursor: text;
327
+ padding: 0.25em 0;
328
+ display: block;
329
+ }
330
+
331
+ .tiptap [data-type="detailsContent"] {
332
+ margin-top: 0.5em;
333
+ padding-left: 0.5em;
334
+ border-left: 2px solid var(--border);
335
+ }
336
+
337
+ .tiptap [data-type="detailsContent"][hidden] {
338
+ display: none;
339
+ }
340
+
341
+ /* 게시물 렌더링: 네이티브 <details> */
342
+ .tiptap details {
343
+ border: 1px solid var(--border);
344
+ border-radius: 8px;
345
+ padding: 0.5em 1em;
346
+ margin: 0.75em 0;
347
+ background: var(--background);
348
+ }
349
+
350
+ .tiptap details summary {
351
+ font-weight: 600;
352
+ cursor: pointer;
353
+ padding: 0.25em 0;
354
+ user-select: none;
355
+ }
356
+
357
+ .tiptap details summary::marker {
358
+ color: var(--muted-foreground);
359
+ }
360
+
361
+ .tiptap details[open] > *:not(summary) {
362
+ margin-top: 0.5em;
363
+ }
364
+
365
+ /* ── YouTube 임베드 ── */
366
+ .tiptap div[data-youtube-video] {
367
+ margin: 1em 0;
368
+ }
369
+
370
+ .tiptap div[data-youtube-video] iframe {
371
+ width: 100%;
372
+ aspect-ratio: 16 / 9;
373
+ border-radius: 8px;
374
+ border: none;
375
+ }
376
+
377
+
378
+ /* ── 테이블 ── */
379
+ .tiptap table {
380
+ border-collapse: collapse;
381
+ margin: 1em 0;
382
+ table-layout: fixed;
383
+ border: 2px solid var(--border);
384
+ max-width: 100%;
385
+ }
386
+
387
+ .tiptap .tableWrapper {
388
+ overflow-x: auto;
389
+ max-width: 100%;
390
+ }
391
+
392
+ .tiptap th,
393
+ .tiptap td {
394
+ border: 1px solid var(--border);
395
+ padding: 0.6em 0.8em;
396
+ min-width: 60px;
397
+ vertical-align: middle;
398
+ position: relative;
399
+ box-sizing: border-box;
400
+ overflow: hidden;
401
+ word-break: break-word;
402
+ line-height: 1.5;
403
+ }
404
+
405
+ .tiptap th {
406
+ background: var(--muted);
407
+ font-weight: 600;
408
+ text-align: left;
409
+ }
410
+
411
+ .tiptap td {
412
+ background: var(--background);
413
+ }
414
+
415
+ .tiptap .selectedCell::after {
416
+ content: "";
417
+ position: absolute;
418
+ inset: 0;
419
+ background: var(--primary);
420
+ opacity: 0.12;
421
+ pointer-events: none;
422
+ z-index: 1;
423
+ }
424
+
425
+ .tiptap .column-resize-handle {
426
+ position: absolute;
427
+ right: -3px;
428
+ top: 0;
429
+ bottom: 0;
430
+ width: 6px;
431
+ background: var(--primary);
432
+ opacity: 0;
433
+ cursor: col-resize;
434
+ z-index: 20;
435
+ border-radius: 2px;
436
+ transition: opacity 0.15s;
437
+ }
438
+
439
+ .tiptap table:hover .column-resize-handle {
440
+ opacity: 0.4;
441
+ }
442
+
443
+ .tiptap .column-resize-handle:hover {
444
+ opacity: 1;
445
+ }
446
+
447
+ .tiptap.resize-cursor {
448
+ cursor: col-resize;
449
+ }
450
+
451
+ .tableWrapper {
452
+ overflow-x: auto;
453
+ overflow-y: hidden;
454
+ margin: 1em 0;
455
+ max-width: 100%;
456
+ padding-right: 4px; /* resize handle이 잘리지 않도록 */
457
+ }
458
+
459
+ /* 테이블 셀 내부 prosemirror 기본 컨트롤 숨기기 */
460
+ .tiptap table .grip-column,
461
+ .tiptap table .grip-row,
462
+ .tiptap table .grip-table {
463
+ display: none !important;
464
+ }
465
+
466
+ /* ── 플레이스홀더 (Notion-style) ── */
467
+ .tiptap .is-empty::before {
468
+ content: attr(data-placeholder);
469
+ float: left;
470
+ color: var(--muted-foreground);
471
+ opacity: 0.5;
472
+ pointer-events: none;
473
+ height: 0;
474
+ }
475
+
476
+ .tiptap h1.is-empty::before {
477
+ font-size: 2rem;
478
+ font-weight: 700;
479
+ }
480
+
481
+ .tiptap h2.is-empty::before {
482
+ font-size: 1.5rem;
483
+ font-weight: 600;
484
+ }
485
+
486
+ .tiptap h3.is-empty::before {
487
+ font-size: 1.25rem;
488
+ font-weight: 600;
489
+ }
490
+
491
+ /* ── Gap cursor ── */
492
+ .ProseMirror-gapcursor {
493
+ display: none;
494
+ pointer-events: none;
495
+ position: relative;
496
+ }
497
+
498
+ .ProseMirror-gapcursor::after {
499
+ content: "";
500
+ display: block;
501
+ position: absolute;
502
+ top: -2px;
503
+ width: 20px;
504
+ border-top: 1px solid var(--foreground);
505
+ animation: ProseMirror-cursor-blink 1.1s steps(2, start) infinite;
506
+ }
507
+
508
+ @keyframes ProseMirror-cursor-blink {
509
+ to {
510
+ visibility: hidden;
511
+ }
512
+ }
513
+
514
+ .ProseMirror-focused .ProseMirror-gapcursor {
515
+ display: block;
516
+ }
517
+
518
+ /* ── Drop cursor ── */
519
+ .ProseMirror-dropcursor {
520
+ border-top: 2px solid var(--primary);
521
+ margin: -1px 0;
522
+ }
523
+
524
+ /* ── 선택 (텍스트) ── */
525
+ .tiptap ::selection {
526
+ background-color: color-mix(in srgb, var(--primary) 15%, transparent);
527
+ }
528
+
529
+ /* ── Node range selection (드래그 시) ── */
530
+ .ProseMirror-noderangeselection *::selection {
531
+ background: transparent;
532
+ }
533
+
534
+ .ProseMirror-noderangeselection * {
535
+ caret-color: transparent;
536
+ }
537
+
538
+ /* ── 선택된 노드 하이라이트 ── */
539
+ .ProseMirror-selectednode,
540
+ .ProseMirror-selectednoderange {
541
+ position: relative;
542
+ }
543
+
544
+ .ProseMirror-selectednode:not(img):not([data-youtube-video]):not([data-type="pdfBlock"])::before,
545
+ .ProseMirror-selectednoderange::before {
546
+ content: "";
547
+ position: absolute;
548
+ inset: -0.25rem;
549
+ background-color: color-mix(in srgb, var(--primary) 10%, transparent);
550
+ border: 2px solid color-mix(in srgb, var(--primary) 30%, transparent);
551
+ border-radius: 0.5rem;
552
+ pointer-events: none;
553
+ z-index: -1;
554
+ }
555
+
556
+ /* ── BubbleToolbar z-index ── */
557
+ .bubble-toolbar-container {
558
+ z-index: 50;
559
+ }
560
+
561
+ /* ── Floating + 버튼 ── */
562
+ .hce-floating-plus {
563
+ display: flex;
564
+ align-items: center;
565
+ justify-content: center;
566
+ width: 24px;
567
+ height: 24px;
568
+ border-radius: 6px;
569
+ border: 1px solid var(--border);
570
+ background: var(--background);
571
+ color: var(--muted-foreground);
572
+ cursor: pointer;
573
+ transition: all 0.15s;
574
+ }
575
+
576
+ .hce-floating-plus:hover {
577
+ color: var(--foreground);
578
+ border-color: var(--foreground);
579
+ background: var(--muted);
580
+ }
581
+
582
+ /* ── Drag handle ── */
583
+ .hce-drag-handle {
584
+ display: flex;
585
+ align-items: center;
586
+ gap: 2px;
587
+ }
588
+
589
+ .hce-drag-handle__plus {
590
+ display: flex;
591
+ align-items: center;
592
+ justify-content: center;
593
+ padding: 4px;
594
+ border-radius: 4px;
595
+ border: none;
596
+ background: transparent;
597
+ color: var(--muted-foreground);
598
+ opacity: 0.5;
599
+ cursor: pointer;
600
+ transition: opacity 0.15s, background-color 0.15s;
601
+ }
602
+
603
+ .hce-drag-handle__plus:hover {
604
+ opacity: 1;
605
+ background: var(--muted);
606
+ }
607
+
608
+ .hce-drag-handle__grip {
609
+ display: flex;
610
+ align-items: center;
611
+ justify-content: center;
612
+ padding: 2px;
613
+ border-radius: 4px;
614
+ color: var(--muted-foreground);
615
+ opacity: 0.5;
616
+ cursor: grab;
617
+ transition: opacity 0.15s, background-color 0.15s;
618
+ }
619
+
620
+ .hce-drag-handle__grip:hover {
621
+ opacity: 1;
622
+ background: var(--muted);
623
+ }
624
+
625
+ .hce-drag-handle__grip:active {
626
+ cursor: grabbing;
627
+ }
628
+
629
+ /* ── 컬럼 레이아웃 ── */
630
+ .tiptap div[data-type="columns"] {
631
+ display: grid;
632
+ gap: 24px;
633
+ margin: 1em 0;
634
+ }
635
+
636
+ .tiptap div[data-type="columns"][data-columns="2"] {
637
+ grid-template-columns: 1fr 1fr;
638
+ }
639
+
640
+ .tiptap div[data-type="columns"][data-columns="3"] {
641
+ grid-template-columns: 1fr 1fr 1fr;
642
+ }
643
+
644
+ .tiptap div[data-type="column"] {
645
+ min-width: 0;
646
+ padding: 4px 8px;
647
+ border: 1px dashed var(--border);
648
+ border-radius: 8px;
649
+ }
650
+
651
+ .tiptap div[data-type="column"]:focus-within {
652
+ border-color: var(--primary);
653
+ border-style: solid;
654
+ }
655
+
656
+ .tiptap div[data-type="columns"].ProseMirror-selectednode {
657
+ outline: 2px solid color-mix(in srgb, var(--primary) 30%, transparent);
658
+ border-radius: 8px;
659
+ }
660
+
661
+ @media (max-width: 640px) {
662
+ .tiptap div[data-type="columns"] {
663
+ grid-template-columns: 1fr !important;
664
+ }
665
+ }
666
+
667
+ /* ── 툴바 버튼 CSS tooltip (data-tooltip 속성 기반) ── */
668
+ .hce-editor-wrapper button[data-tooltip] {
669
+ position: relative;
670
+ }
671
+
672
+ .hce-editor-wrapper button[data-tooltip]:hover::after {
673
+ content: attr(data-tooltip);
674
+ position: absolute;
675
+ top: 100%;
676
+ left: 50%;
677
+ transform: translateX(-50%);
678
+ margin-top: 6px;
679
+ padding: 4px 8px;
680
+ font-size: 11px;
681
+ line-height: 1.2;
682
+ white-space: nowrap;
683
+ color: #fff;
684
+ background: #1e293b;
685
+ border-radius: 6px;
686
+ pointer-events: none;
687
+ z-index: 60;
688
+ }
689
+
690
+ /* ── 테이블 버블 메뉴 tooltip ── */
691
+ .hce-table-btn-wrap { position: relative; display: inline-flex; }
692
+ .hce-table-btn-tooltip {
693
+ position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%);
694
+ margin-bottom: 6px; padding: 4px 8px; font-size: 11px; line-height: 1.2;
695
+ white-space: nowrap; color: #fff; background: #1e293b; border-radius: 6px;
696
+ pointer-events: none; opacity: 0; transition: opacity 0.15s; z-index: 60;
697
+ }
698
+ .hce-table-btn-wrap:hover .hce-table-btn-tooltip { opacity: 1; }