smeditor 0.1.0 → 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.
@@ -0,0 +1,1218 @@
1
+ /* SMEditor default theme tokens.
2
+ *
3
+ * Two ways to pick a theme:
4
+ *
5
+ * 1. Do nothing → the editor follows `prefers-color-scheme`.
6
+ * 2. Set `data-theme="light"` or `data-theme="dark"` on any element
7
+ * that wraps the editor (or on <html>). The explicit attribute
8
+ * wins over the media query.
9
+ *
10
+ * Why both: developers want auto for new users (matches the rest of
11
+ * their app), but a theme toggle in product UI needs explicit control
12
+ * that survives reloads. Supporting both lets either pattern work.
13
+ */
14
+
15
+ /* ---------------------------------------------------------------------------
16
+ * Light theme — the default. Applied to :root, to anything explicitly tagged
17
+ * light, and inherited by everything not explicitly tagged dark.
18
+ * ------------------------------------------------------------------------ */
19
+ :root,
20
+ [data-theme="light"] {
21
+ /* Surface */
22
+ --sme-color-surface: #ffffff;
23
+ --sme-color-surface-muted: #f7f7f8;
24
+ --sme-color-border: #e5e7eb;
25
+ --sme-color-border-strong: #c7c9d1;
26
+
27
+ /* Text */
28
+ --sme-color-text: #1a1a1f;
29
+ --sme-color-text-muted: #6b7280;
30
+ --sme-color-placeholder: #9ca3af;
31
+
32
+ /* Accent */
33
+ --sme-color-accent: #5b21b6;
34
+ --sme-color-accent-soft: #ede9fe;
35
+ --sme-color-primary: var(--sme-color-accent);
36
+ --sme-color-focus: #7c3aed;
37
+ --sme-color-danger: #b91c1c;
38
+ --sme-color-danger-soft: #fee2e2;
39
+
40
+ /* Elevation */
41
+ --sme-shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.08);
42
+ --sme-shadow-md: 0 10px 38px -10px rgba(15, 23, 42, 0.16),
43
+ 0 10px 20px -15px rgba(15, 23, 42, 0.12);
44
+ }
45
+
46
+ /* Common (non-color) tokens — shared by both themes. */
47
+ :root {
48
+ /* Spacing */
49
+ --sme-space-1: 4px;
50
+ --sme-space-2: 8px;
51
+ --sme-space-3: 12px;
52
+ --sme-space-4: 16px;
53
+
54
+ /* Radius */
55
+ --sme-radius-sm: 4px;
56
+ --sme-radius-md: 8px;
57
+ --sme-radius-lg: 12px;
58
+
59
+ /* Type */
60
+ --sme-font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
61
+ "Helvetica Neue", Arial, sans-serif;
62
+ --sme-font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
63
+ "Liberation Mono", monospace;
64
+
65
+ --sme-text-base: 16px;
66
+ --sme-text-sm: 14px;
67
+ --sme-leading: 1.6;
68
+
69
+ /* Controls */
70
+ --sme-control-height: 32px;
71
+ --sme-focus-ring: 0 0 0 3px color-mix(
72
+ in srgb,
73
+ var(--sme-color-focus) 22%,
74
+ transparent
75
+ );
76
+
77
+ /* Indent step (block-level indentation) */
78
+ --sme-indent-step: 24px;
79
+ }
80
+
81
+ /* ---------------------------------------------------------------------------
82
+ * Dark theme — applied when explicitly tagged, OR via media query when
83
+ * no explicit theme is set on the document.
84
+ * ------------------------------------------------------------------------ */
85
+ [data-theme="dark"] {
86
+ --sme-color-surface: #0f1115;
87
+ --sme-color-surface-muted: #1a1d23;
88
+ --sme-color-border: #2a2d35;
89
+ --sme-color-border-strong: #3a3f49;
90
+ --sme-color-text: #e7e9ee;
91
+ --sme-color-text-muted: #9aa0aa;
92
+ --sme-color-placeholder: #5c606b;
93
+ --sme-color-accent: #a78bfa;
94
+ --sme-color-accent-soft: #2a1d54;
95
+ --sme-color-primary: var(--sme-color-accent);
96
+ --sme-color-focus: #c4b5fd;
97
+ --sme-color-danger: #fca5a5;
98
+ --sme-color-danger-soft: #451a1a;
99
+ --sme-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.35);
100
+ --sme-shadow-md: 0 10px 38px -10px rgba(0, 0, 0, 0.5),
101
+ 0 10px 20px -15px rgba(0, 0, 0, 0.45);
102
+ }
103
+
104
+ /* Auto-dark: only when the document root has no explicit override. The
105
+ * `:not([data-theme])` guard means a `data-theme="light"` setting on
106
+ * any ancestor wins, even at night. */
107
+ @media (prefers-color-scheme: dark) {
108
+ :root:not([data-theme="light"]) {
109
+ --sme-color-surface: #0f1115;
110
+ --sme-color-surface-muted: #1a1d23;
111
+ --sme-color-border: #2a2d35;
112
+ --sme-color-border-strong: #3a3f49;
113
+ --sme-color-text: #e7e9ee;
114
+ --sme-color-text-muted: #9aa0aa;
115
+ --sme-color-placeholder: #5c606b;
116
+ --sme-color-accent: #a78bfa;
117
+ --sme-color-accent-soft: #2a1d54;
118
+ --sme-color-primary: var(--sme-color-accent);
119
+ --sme-color-focus: #c4b5fd;
120
+ --sme-color-danger: #fca5a5;
121
+ --sme-color-danger-soft: #451a1a;
122
+ --sme-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.35);
123
+ --sme-shadow-md: 0 10px 38px -10px rgba(0, 0, 0, 0.5),
124
+ 0 10px 20px -15px rgba(0, 0, 0, 0.45);
125
+ }
126
+ }
127
+
128
+ /* SMEditor default theme.
129
+ *
130
+ * Imports tokens, then styles the structural classes set by core
131
+ * (`.smeditor-editor`, `.smeditor-content`) and the toolbar
132
+ * primitives (`.smeditor-toolbar`, `.smeditor-button`, `.smeditor-dropdown`).
133
+ */
134
+
135
+ .smeditor {
136
+ font-family: var(--sme-font-sans);
137
+ color: var(--sme-color-text);
138
+ background: var(--sme-color-surface);
139
+ border: 1px solid var(--sme-color-border);
140
+ border-radius: var(--sme-radius-md);
141
+ overflow: visible; /* allow dropdown popovers to escape */
142
+ }
143
+
144
+ /* ===========================================================================
145
+ * Toolbar
146
+ * ======================================================================== */
147
+
148
+ .smeditor-toolbar {
149
+ display: flex;
150
+ flex-wrap: wrap;
151
+ align-items: center;
152
+ gap: 2px;
153
+ padding: var(--sme-space-2);
154
+ background: var(--sme-color-surface-muted);
155
+ border-bottom: 1px solid var(--sme-color-border);
156
+ }
157
+
158
+ .smeditor-toolbar__divider {
159
+ display: inline-block;
160
+ width: 1px;
161
+ height: 22px;
162
+ background: var(--sme-color-border);
163
+ margin: 0 var(--sme-space-1);
164
+ }
165
+
166
+ .smeditor-button {
167
+ appearance: none;
168
+ border: 1px solid transparent;
169
+ background: transparent;
170
+ color: var(--sme-color-text);
171
+ padding: 6px 8px;
172
+ border-radius: var(--sme-radius-sm);
173
+ font: inherit;
174
+ font-size: var(--sme-text-sm);
175
+ line-height: 1;
176
+ cursor: pointer;
177
+ min-width: 30px;
178
+ height: 30px;
179
+ display: inline-flex;
180
+ align-items: center;
181
+ justify-content: center;
182
+ gap: 4px;
183
+ transition: background-color 80ms ease, color 80ms ease;
184
+ }
185
+
186
+ .smeditor-button:hover {
187
+ background: var(--sme-color-border);
188
+ }
189
+
190
+ .smeditor-button:focus-visible {
191
+ outline: 2px solid var(--sme-color-accent);
192
+ outline-offset: 1px;
193
+ }
194
+
195
+ .smeditor-button.is-active,
196
+ .smeditor-button[data-active="true"] {
197
+ background: var(--sme-color-accent-soft);
198
+ color: var(--sme-color-accent);
199
+ border-color: transparent;
200
+ }
201
+
202
+ .smeditor-button:disabled,
203
+ .smeditor-button[aria-disabled="true"] {
204
+ opacity: 0.4;
205
+ cursor: not-allowed;
206
+ }
207
+
208
+ /* ===========================================================================
209
+ * Dropdown
210
+ * ======================================================================== */
211
+
212
+ .smeditor-dropdown {
213
+ position: relative;
214
+ display: inline-block;
215
+ }
216
+
217
+ .smeditor-dropdown__trigger {
218
+ padding-right: 6px;
219
+ }
220
+
221
+ .smeditor-dropdown__label {
222
+ display: inline-flex;
223
+ align-items: center;
224
+ }
225
+
226
+ .smeditor-dropdown__summary {
227
+ font-weight: 600;
228
+ font-size: 11px;
229
+ letter-spacing: 0.02em;
230
+ margin-left: 2px;
231
+ color: inherit;
232
+ }
233
+
234
+ .smeditor-dropdown__chevron {
235
+ font-size: 9px;
236
+ line-height: 1;
237
+ margin-left: 1px;
238
+ opacity: 0.7;
239
+ }
240
+
241
+ .smeditor-dropdown.is-open .smeditor-dropdown__chevron {
242
+ transform: rotate(180deg);
243
+ }
244
+
245
+ .smeditor-dropdown__menu {
246
+ position: absolute;
247
+ top: calc(100% + 4px);
248
+ left: 0;
249
+ min-width: 200px;
250
+ z-index: 50;
251
+ background: var(--sme-color-surface);
252
+ border: 1px solid var(--sme-color-border);
253
+ border-radius: var(--sme-radius-md);
254
+ box-shadow:
255
+ 0 10px 38px -10px rgba(22, 23, 24, 0.15),
256
+ 0 10px 20px -15px rgba(22, 23, 24, 0.1);
257
+ padding: 4px;
258
+ display: flex;
259
+ flex-direction: column;
260
+ gap: 1px;
261
+ animation: smeditor-dropdown-in 100ms ease-out;
262
+ }
263
+
264
+ .smeditor-dropdown__menu.is-right {
265
+ left: auto;
266
+ right: 0;
267
+ }
268
+
269
+ @keyframes smeditor-dropdown-in {
270
+ from {
271
+ opacity: 0;
272
+ transform: translateY(-4px);
273
+ }
274
+ to {
275
+ opacity: 1;
276
+ transform: translateY(0);
277
+ }
278
+ }
279
+
280
+ .smeditor-dropdown__item {
281
+ appearance: none;
282
+ border: 0;
283
+ background: transparent;
284
+ color: var(--sme-color-text);
285
+ font: inherit;
286
+ font-size: var(--sme-text-sm);
287
+ padding: 7px 8px;
288
+ border-radius: var(--sme-radius-sm);
289
+ cursor: pointer;
290
+ display: grid;
291
+ grid-template-columns: 20px 1fr auto;
292
+ align-items: center;
293
+ gap: 8px;
294
+ text-align: left;
295
+ }
296
+
297
+ .smeditor-dropdown__item:hover {
298
+ background: var(--sme-color-surface-muted);
299
+ }
300
+
301
+ .smeditor-dropdown__item.is-active {
302
+ color: var(--sme-color-accent);
303
+ background: var(--sme-color-accent-soft);
304
+ }
305
+
306
+ .smeditor-dropdown__item-icon {
307
+ display: inline-flex;
308
+ align-items: center;
309
+ justify-content: center;
310
+ color: var(--sme-color-text-muted);
311
+ }
312
+
313
+ .smeditor-dropdown__item.is-active .smeditor-dropdown__item-icon {
314
+ color: var(--sme-color-accent);
315
+ }
316
+
317
+ .smeditor-dropdown__item-shortcut {
318
+ color: var(--sme-color-text-muted);
319
+ font-size: 11px;
320
+ font-family: var(--sme-font-mono);
321
+ }
322
+
323
+ .smeditor-dropdown__separator {
324
+ height: 1px;
325
+ background: var(--sme-color-border);
326
+ margin: 4px 0;
327
+ }
328
+
329
+ /* Color swatch grid inside the color-picker dropdowns */
330
+ .smeditor-color-grid {
331
+ display: grid;
332
+ grid-template-columns: repeat(8, 1fr);
333
+ gap: 4px;
334
+ padding: 6px;
335
+ }
336
+ .smeditor-color-grid__swatch {
337
+ width: 22px;
338
+ height: 22px;
339
+ border-radius: 4px;
340
+ border: 1px solid var(--sme-color-border);
341
+ cursor: pointer;
342
+ appearance: none;
343
+ padding: 0;
344
+ transition: transform 80ms ease;
345
+ }
346
+ .smeditor-color-grid__swatch:hover {
347
+ transform: scale(1.1);
348
+ }
349
+ .smeditor-color-grid__swatch--clear {
350
+ background:
351
+ linear-gradient(45deg, transparent 46%, #d23 46% 54%, transparent 54%),
352
+ var(--sme-color-surface);
353
+ }
354
+
355
+ /* ===========================================================================
356
+ * Editor surface
357
+ * ======================================================================== */
358
+
359
+ .smeditor-content,
360
+ .smeditor-editor {
361
+ padding: var(--sme-space-4);
362
+ font-size: var(--sme-text-base);
363
+ line-height: var(--sme-leading);
364
+ min-height: 8em;
365
+ outline: none;
366
+ }
367
+
368
+ .smeditor-editor[contenteditable="false"] {
369
+ background: var(--sme-color-surface-muted);
370
+ cursor: default;
371
+ }
372
+
373
+ .smeditor-editor :where(p) {
374
+ margin: 0 0 var(--sme-space-3) 0;
375
+ }
376
+ .smeditor-editor :where(p:last-child) {
377
+ margin-bottom: 0;
378
+ }
379
+
380
+ .smeditor-editor :where(h1, h2, h3, h4, h5, h6) {
381
+ margin: var(--sme-space-4) 0 var(--sme-space-2);
382
+ line-height: 1.25;
383
+ font-weight: 600;
384
+ }
385
+ .smeditor-editor :where(h1) { font-size: 1.875rem; }
386
+ .smeditor-editor :where(h2) { font-size: 1.5rem; }
387
+ .smeditor-editor :where(h3) { font-size: 1.25rem; }
388
+ .smeditor-editor :where(h4) { font-size: 1.125rem; }
389
+ .smeditor-editor :where(h5, h6) { font-size: 1rem; }
390
+
391
+ /* Marks */
392
+ .smeditor-editor :where(strong) { font-weight: 700; }
393
+ .smeditor-editor :where(em) { font-style: italic; }
394
+ .smeditor-editor :where(u) { text-decoration: underline; text-underline-offset: 2px; }
395
+ .smeditor-editor :where(s, del) { text-decoration: line-through; }
396
+ .smeditor-editor :where(sub) { font-size: 0.75em; vertical-align: sub; }
397
+ .smeditor-editor :where(sup) { font-size: 0.75em; vertical-align: super; }
398
+ .smeditor-editor :where(code) {
399
+ font-family: var(--sme-font-mono);
400
+ font-size: 0.875em;
401
+ background: var(--sme-color-surface-muted);
402
+ border: 1px solid var(--sme-color-border);
403
+ border-radius: 3px;
404
+ padding: 0.05em 0.35em;
405
+ }
406
+ .smeditor-editor :where(mark) {
407
+ background: #fff59d;
408
+ color: inherit;
409
+ padding: 0 0.05em;
410
+ border-radius: 2px;
411
+ }
412
+ [data-theme="dark"] .smeditor-editor :where(mark) {
413
+ background: #5d4a00;
414
+ color: #ffeb95;
415
+ }
416
+
417
+ .smeditor-editor :where(a) {
418
+ color: var(--sme-color-accent);
419
+ text-decoration: underline;
420
+ text-underline-offset: 2px;
421
+ }
422
+
423
+ /* Horizontal rule ------------------------------------------------------- */
424
+ .smeditor-editor :where(hr) {
425
+ border: 0;
426
+ height: 1px;
427
+ background: var(--sme-color-border-strong);
428
+ margin: var(--sme-space-4) 0;
429
+ }
430
+
431
+ /* Image ----------------------------------------------------------------- */
432
+ .smeditor-editor :where(img.smeditor-image) {
433
+ display: block;
434
+ max-width: 100%;
435
+ height: auto;
436
+ margin: var(--sme-space-3) 0;
437
+ border-radius: var(--sme-radius-sm);
438
+ }
439
+ .smeditor-editor :where(img.smeditor-image[data-align="left"]) {
440
+ margin-right: auto;
441
+ }
442
+ .smeditor-editor :where(img.smeditor-image[data-align="center"]) {
443
+ margin-left: auto;
444
+ margin-right: auto;
445
+ }
446
+ .smeditor-editor :where(img.smeditor-image[data-align="right"]) {
447
+ margin-left: auto;
448
+ }
449
+ /* Selected/focused image gets a subtle ring so the context toolbar
450
+ * has a clear referent. The editor sets no class here yet — this
451
+ * styles the browser's native :focus where possible. */
452
+ .smeditor-editor :where(img.smeditor-image):hover {
453
+ outline: 2px solid var(--sme-color-accent-soft);
454
+ outline-offset: 2px;
455
+ }
456
+
457
+ /* Captioned image — <figure> + <figcaption> */
458
+ .smeditor-editor :where(figure.smeditor-figure) {
459
+ margin: var(--sme-space-3) 0;
460
+ }
461
+ .smeditor-editor :where(figure.smeditor-figure img.smeditor-image) {
462
+ margin: 0;
463
+ }
464
+ .smeditor-editor :where(figure.smeditor-figure[data-align="center"]) {
465
+ text-align: center;
466
+ }
467
+ .smeditor-editor :where(figure.smeditor-figure[data-align="right"]) {
468
+ text-align: right;
469
+ }
470
+ .smeditor-editor
471
+ :where(
472
+ figure.smeditor-figure[data-align="center"] img.smeditor-image
473
+ ) {
474
+ margin-left: auto;
475
+ margin-right: auto;
476
+ }
477
+ .smeditor-editor
478
+ :where(figure.smeditor-figure[data-align="right"] img.smeditor-image) {
479
+ margin-left: auto;
480
+ }
481
+ .smeditor-editor :where(figcaption.smeditor-figcaption) {
482
+ margin-top: var(--sme-space-2);
483
+ font-size: var(--sme-text-sm);
484
+ color: var(--sme-color-text-muted);
485
+ text-align: center;
486
+ }
487
+
488
+ /* Context toolbar (image, future: table) — a compact floating bar */
489
+ .smeditor-toolbar--context {
490
+ border: 1px solid var(--sme-color-border);
491
+ border-radius: var(--sme-radius-md);
492
+ box-shadow: 0 4px 16px -6px rgba(22, 23, 24, 0.2);
493
+ display: inline-flex;
494
+ margin-top: var(--sme-space-2);
495
+ }
496
+
497
+ /* ===========================================================================
498
+ * Floating menus — bubble (over selection) and slash (block insert)
499
+ * ======================================================================== */
500
+
501
+ .smeditor-floating {
502
+ /* position/top/left/opacity are set inline by the Floating component */
503
+ animation: smeditor-floating-in 90ms ease-out;
504
+ }
505
+
506
+ @keyframes smeditor-floating-in {
507
+ from {
508
+ opacity: 0;
509
+ transform: translateY(2px);
510
+ }
511
+ to {
512
+ opacity: 1;
513
+ transform: translateY(0);
514
+ }
515
+ }
516
+
517
+ /* Bubble menu — a horizontal row of inline-format buttons */
518
+ .smeditor-bubble-menu {
519
+ display: inline-flex;
520
+ align-items: center;
521
+ gap: 2px;
522
+ padding: 4px;
523
+ background: var(--sme-color-surface);
524
+ border: 1px solid var(--sme-color-border);
525
+ border-radius: var(--sme-radius-md);
526
+ box-shadow:
527
+ 0 8px 28px -8px rgba(22, 23, 24, 0.28),
528
+ 0 2px 8px -4px rgba(22, 23, 24, 0.16);
529
+ }
530
+
531
+ /* Slash menu — a vertical list of block-insert commands */
532
+ .smeditor-slash-menu {
533
+ display: flex;
534
+ flex-direction: column;
535
+ gap: 1px;
536
+ min-width: 220px;
537
+ max-height: 320px;
538
+ overflow-y: auto;
539
+ padding: 4px;
540
+ background: var(--sme-color-surface);
541
+ border: 1px solid var(--sme-color-border);
542
+ border-radius: var(--sme-radius-md);
543
+ box-shadow:
544
+ 0 10px 38px -10px rgba(22, 23, 24, 0.22),
545
+ 0 10px 20px -15px rgba(22, 23, 24, 0.12);
546
+ }
547
+
548
+ .smeditor-slash-item {
549
+ appearance: none;
550
+ border: 0;
551
+ background: transparent;
552
+ color: var(--sme-color-text);
553
+ font: inherit;
554
+ font-size: var(--sme-text-sm);
555
+ padding: 7px 8px;
556
+ border-radius: var(--sme-radius-sm);
557
+ cursor: pointer;
558
+ display: grid;
559
+ grid-template-columns: 22px 1fr auto;
560
+ align-items: center;
561
+ gap: 8px;
562
+ text-align: left;
563
+ }
564
+
565
+ .smeditor-slash-item.is-active {
566
+ background: var(--sme-color-accent-soft);
567
+ color: var(--sme-color-accent);
568
+ }
569
+
570
+ .smeditor-slash-item__icon {
571
+ display: inline-flex;
572
+ align-items: center;
573
+ justify-content: center;
574
+ color: var(--sme-color-text-muted);
575
+ }
576
+ .smeditor-slash-item.is-active .smeditor-slash-item__icon {
577
+ color: var(--sme-color-accent);
578
+ }
579
+
580
+ .smeditor-slash-item__hint {
581
+ color: var(--sme-color-text-muted);
582
+ font-size: 11px;
583
+ font-family: var(--sme-font-mono);
584
+ }
585
+
586
+ /* Table ----------------------------------------------------------------- */
587
+ .smeditor-editor :where(table.smeditor-table) {
588
+ border-collapse: collapse;
589
+ width: 100%;
590
+ margin: var(--sme-space-3) 0;
591
+ font-size: var(--sme-text-sm);
592
+ table-layout: fixed;
593
+ }
594
+ .smeditor-editor :where(table.smeditor-table td),
595
+ .smeditor-editor :where(table.smeditor-table th) {
596
+ border: 1px solid var(--sme-color-border-strong);
597
+ padding: 6px 10px;
598
+ vertical-align: top;
599
+ min-width: 3em;
600
+ }
601
+ .smeditor-editor :where(table.smeditor-table th) {
602
+ background: var(--sme-color-surface-muted);
603
+ font-weight: 600;
604
+ text-align: left;
605
+ }
606
+ .smeditor-editor :where(table.smeditor-table td > p:last-child),
607
+ .smeditor-editor :where(table.smeditor-table th > p:last-child) {
608
+ margin-bottom: 0;
609
+ }
610
+ /* First child hugs the cell's top padding edge, so cell text starts
611
+ * where the user expects rather than a heading-margin below it. */
612
+ .smeditor-editor :where(table.smeditor-table td > :first-child),
613
+ .smeditor-editor :where(table.smeditor-table th > :first-child) {
614
+ margin-top: 0;
615
+ }
616
+ /* An empty cell / paragraph still carries a filler <br>; give leaf
617
+ * blocks a minimum height so the caret has somewhere to land. */
618
+ .smeditor-editor :where(p, h1, h2, h3, h4, h5, h6) {
619
+ min-height: 1em;
620
+ }
621
+
622
+ /* Cell-range selection — cells picked by drag / shift-click */
623
+ .smeditor-editor
624
+ :where(
625
+ table.smeditor-table td.smeditor-cell--selected,
626
+ table.smeditor-table th.smeditor-cell--selected
627
+ ) {
628
+ background: var(--sme-color-accent-soft);
629
+ box-shadow: inset 0 0 0 1px var(--sme-color-accent);
630
+ }
631
+
632
+ /* Comment mark — commented text (§14) */
633
+ .smeditor-editor :where(.smeditor-comment) {
634
+ background: color-mix(in srgb, #fbbf24 28%, transparent);
635
+ border-bottom: 2px solid #f59e0b;
636
+ cursor: pointer;
637
+ }
638
+ .smeditor-editor :where(.smeditor-comment--resolved) {
639
+ background: transparent;
640
+ border-bottom: 2px solid var(--sme-color-border-strong);
641
+ }
642
+
643
+ /* Mention pill (§14) */
644
+ .smeditor-editor :where(.smeditor-mention) {
645
+ display: inline;
646
+ padding: 1px 5px;
647
+ border-radius: 4px;
648
+ background: var(--sme-color-accent-soft);
649
+ color: var(--sme-color-accent);
650
+ font-weight: 500;
651
+ white-space: nowrap;
652
+ cursor: default;
653
+ }
654
+
655
+ /* Mention menu — @-triggered candidate picker */
656
+ .smeditor-mention-menu {
657
+ display: flex;
658
+ flex-direction: column;
659
+ gap: 1px;
660
+ min-width: 200px;
661
+ max-height: 280px;
662
+ overflow-y: auto;
663
+ padding: 4px;
664
+ background: var(--sme-color-surface);
665
+ border: 1px solid var(--sme-color-border);
666
+ border-radius: var(--sme-radius-md);
667
+ box-shadow:
668
+ 0 10px 38px -10px rgba(22, 23, 24, 0.22),
669
+ 0 10px 20px -15px rgba(22, 23, 24, 0.12);
670
+ }
671
+ .smeditor-mention-item {
672
+ appearance: none;
673
+ border: 0;
674
+ background: transparent;
675
+ color: var(--sme-color-text);
676
+ font: inherit;
677
+ font-size: var(--sme-text-sm);
678
+ padding: 6px 8px;
679
+ border-radius: var(--sme-radius-sm);
680
+ cursor: pointer;
681
+ display: flex;
682
+ flex-direction: column;
683
+ gap: 1px;
684
+ text-align: left;
685
+ }
686
+ .smeditor-mention-item.is-active {
687
+ background: var(--sme-color-accent-soft);
688
+ color: var(--sme-color-accent);
689
+ }
690
+ .smeditor-mention-item__hint {
691
+ font-size: 11px;
692
+ color: var(--sme-color-text-muted);
693
+ }
694
+ .smeditor-mention-item.is-active .smeditor-mention-item__hint {
695
+ color: var(--sme-color-accent);
696
+ }
697
+
698
+ /* Table size picker (inside the TableButton dropdown) */
699
+ .smeditor-table-picker {
700
+ display: grid;
701
+ grid-template-columns: repeat(6, 18px);
702
+ grid-auto-rows: 18px;
703
+ gap: 3px;
704
+ padding: 8px;
705
+ }
706
+ .smeditor-table-picker__cell {
707
+ appearance: none;
708
+ border: 1px solid var(--sme-color-border-strong);
709
+ background: var(--sme-color-surface);
710
+ border-radius: 2px;
711
+ padding: 0;
712
+ cursor: pointer;
713
+ }
714
+ .smeditor-table-picker__cell.is-on {
715
+ background: var(--sme-color-accent-soft);
716
+ border-color: var(--sme-color-accent);
717
+ }
718
+ .smeditor-table-picker__label {
719
+ text-align: center;
720
+ font-size: var(--sme-text-sm);
721
+ color: var(--sme-color-text-muted);
722
+ padding: 0 8px 8px;
723
+ }
724
+
725
+ /* Image resizer — drag-handle overlay */
726
+ .smeditor-image-resizer {
727
+ /* box outline so the user sees what's being resized */
728
+ outline: 1.5px solid var(--sme-color-accent);
729
+ outline-offset: 0;
730
+ }
731
+ .smeditor-image-resizer__handle {
732
+ position: absolute;
733
+ width: 11px;
734
+ height: 11px;
735
+ background: var(--sme-color-surface);
736
+ border: 1.5px solid var(--sme-color-accent);
737
+ border-radius: 2px;
738
+ box-sizing: border-box;
739
+ }
740
+ .smeditor-image-resizer__handle.is-nw {
741
+ top: -6px;
742
+ left: -6px;
743
+ cursor: nwse-resize;
744
+ }
745
+ .smeditor-image-resizer__handle.is-ne {
746
+ top: -6px;
747
+ right: -6px;
748
+ cursor: nesw-resize;
749
+ }
750
+ .smeditor-image-resizer__handle.is-sw {
751
+ bottom: -6px;
752
+ left: -6px;
753
+ cursor: nesw-resize;
754
+ }
755
+ .smeditor-image-resizer__handle.is-se {
756
+ bottom: -6px;
757
+ right: -6px;
758
+ cursor: nwse-resize;
759
+ }
760
+
761
+ /* Table column resizer — thin vertical handles on column borders */
762
+ .smeditor-col-resizer {
763
+ cursor: col-resize;
764
+ background: transparent;
765
+ transition: background-color 80ms ease;
766
+ }
767
+ .smeditor-col-resizer:hover {
768
+ background: color-mix(in srgb, var(--sme-color-accent) 35%, transparent);
769
+ }
770
+
771
+ /* Source view — raw-HTML editing pane (§11) */
772
+ .smeditor-source-view {
773
+ display: flex;
774
+ flex-direction: column;
775
+ gap: var(--sme-space-2);
776
+ }
777
+ .smeditor-source-view__area {
778
+ width: 100%;
779
+ min-height: 320px;
780
+ resize: vertical;
781
+ box-sizing: border-box;
782
+ padding: var(--sme-space-3);
783
+ border: 1px solid var(--sme-color-border);
784
+ border-radius: var(--sme-radius-md);
785
+ background: var(--sme-color-surface-muted);
786
+ color: var(--sme-color-text);
787
+ font-family: var(--sme-font-mono);
788
+ font-size: var(--sme-text-sm);
789
+ line-height: 1.6;
790
+ tab-size: 2;
791
+ }
792
+ .smeditor-source-view__area:focus {
793
+ outline: 2px solid var(--sme-color-accent);
794
+ outline-offset: -1px;
795
+ }
796
+ .smeditor-source-view__actions {
797
+ display: flex;
798
+ gap: var(--sme-space-2);
799
+ justify-content: flex-end;
800
+ }
801
+
802
+ /* Plain text label inside a dropdown trigger (font / size pickers) */
803
+ .smeditor-dropdown__text {
804
+ font-size: var(--sme-text-sm);
805
+ padding: 0 2px;
806
+ }
807
+
808
+ /* Indent ---------------------------------------------------------------- */
809
+ .smeditor-editor [data-indent="1"] { padding-left: calc(var(--sme-indent-step) * 1); }
810
+ .smeditor-editor [data-indent="2"] { padding-left: calc(var(--sme-indent-step) * 2); }
811
+ .smeditor-editor [data-indent="3"] { padding-left: calc(var(--sme-indent-step) * 3); }
812
+ .smeditor-editor [data-indent="4"] { padding-left: calc(var(--sme-indent-step) * 4); }
813
+ .smeditor-editor [data-indent="5"] { padding-left: calc(var(--sme-indent-step) * 5); }
814
+ .smeditor-editor [data-indent="6"] { padding-left: calc(var(--sme-indent-step) * 6); }
815
+ .smeditor-editor [data-indent="7"] { padding-left: calc(var(--sme-indent-step) * 7); }
816
+ .smeditor-editor [data-indent="8"] { padding-left: calc(var(--sme-indent-step) * 8); }
817
+
818
+ /* Lists ---------------------------------------------------------------- */
819
+ .smeditor-editor :where(ul, ol) {
820
+ margin: 0 0 var(--sme-space-3) 0;
821
+ padding-left: 1.5rem;
822
+ }
823
+ .smeditor-editor :where(ul) { list-style: disc; }
824
+ .smeditor-editor :where(ol) { list-style: decimal; }
825
+ .smeditor-editor :where(li) > p {
826
+ margin: 0 0 var(--sme-space-1) 0;
827
+ }
828
+
829
+ /* Task list ------------------------------------------------------------- */
830
+ .smeditor-editor :where(ul.smeditor-task-list) {
831
+ list-style: none;
832
+ padding-left: 0;
833
+ }
834
+ .smeditor-editor :where(li.smeditor-task-item) {
835
+ display: grid;
836
+ grid-template-columns: 22px 1fr;
837
+ gap: 6px;
838
+ align-items: start;
839
+ padding: 2px 0;
840
+ }
841
+ .smeditor-editor :where(li.smeditor-task-item)::before {
842
+ content: "";
843
+ display: inline-block;
844
+ width: 16px;
845
+ height: 16px;
846
+ margin-top: 4px;
847
+ border: 1.5px solid var(--sme-color-border-strong);
848
+ border-radius: 3px;
849
+ background: var(--sme-color-surface);
850
+ cursor: pointer;
851
+ }
852
+ .smeditor-editor :where(li.smeditor-task-item.is-checked)::before {
853
+ background: var(--sme-color-accent);
854
+ border-color: var(--sme-color-accent);
855
+ background-image: linear-gradient(
856
+ 45deg,
857
+ transparent 40%,
858
+ var(--sme-color-surface) 40% 60%,
859
+ transparent 60%
860
+ ),
861
+ linear-gradient(
862
+ -45deg,
863
+ transparent 50%,
864
+ var(--sme-color-surface) 50% 70%,
865
+ transparent 70%
866
+ );
867
+ }
868
+ .smeditor-editor :where(li.smeditor-task-item.is-checked) > p {
869
+ text-decoration: line-through;
870
+ color: var(--sme-color-text-muted);
871
+ }
872
+
873
+ /* Blockquote ------------------------------------------------------------ */
874
+ .smeditor-editor :where(blockquote) {
875
+ margin: 0 0 var(--sme-space-3) 0;
876
+ padding: var(--sme-space-2) var(--sme-space-3);
877
+ border-left: 3px solid var(--sme-color-accent);
878
+ background: var(--sme-color-surface-muted);
879
+ color: var(--sme-color-text-muted);
880
+ border-radius: 0 var(--sme-radius-sm) var(--sme-radius-sm) 0;
881
+ display: flow-root;
882
+ }
883
+ /* Paragraph margins inside blockquotes must not push the text below the
884
+ * visual middle. Keep the first line tight while preserving spacing
885
+ * between multiple paragraphs in the same quote. */
886
+ .smeditor-editor :where(blockquote > p) {
887
+ margin-top: 0;
888
+ margin-bottom: 0;
889
+ }
890
+ .smeditor-editor :where(blockquote > p + p) {
891
+ margin-top: var(--sme-space-2);
892
+ }
893
+ .smeditor-editor :where(blockquote) > :first-child {
894
+ margin-top: 0;
895
+ }
896
+ .smeditor-editor :where(blockquote) > :last-child {
897
+ margin-bottom: 0;
898
+ }
899
+
900
+ /* Code block ------------------------------------------------------------ */
901
+ .smeditor-editor :where(pre) {
902
+ margin: 0 0 var(--sme-space-3) 0;
903
+ padding: var(--sme-space-3);
904
+ background: var(--sme-color-surface-muted);
905
+ border: 1px solid var(--sme-color-border);
906
+ border-radius: var(--sme-radius-sm);
907
+ font-family: var(--sme-font-mono);
908
+ font-size: 0.875em;
909
+ overflow-x: auto;
910
+ white-space: pre-wrap;
911
+ }
912
+ .smeditor-editor :where(pre) :where(code) {
913
+ background: transparent;
914
+ padding: 0;
915
+ border: 0;
916
+ }
917
+
918
+ /* Placeholder ----------------------------------------------------------- */
919
+ .smeditor-editor :where([data-placeholder])::before {
920
+ content: attr(data-placeholder);
921
+ color: var(--sme-color-placeholder);
922
+ pointer-events: none;
923
+ float: left;
924
+ height: 0;
925
+ }
926
+
927
+ /* Track changes (§14) — insertion / deletion marks */
928
+ .smeditor-editor :where(ins.smeditor-insertion) {
929
+ text-decoration: none;
930
+ color: #1a7f37;
931
+ background: rgba(26, 127, 55, 0.12);
932
+ border-radius: 2px;
933
+ }
934
+ .smeditor-editor :where(del.smeditor-deletion) {
935
+ text-decoration: line-through;
936
+ color: #b35900;
937
+ background: rgba(179, 89, 0, 0.1);
938
+ border-radius: 2px;
939
+ }
940
+
941
+ /* Track-changes review panel */
942
+ .smeditor-track-panel {
943
+ display: flex;
944
+ flex-direction: column;
945
+ gap: 8px;
946
+ font-size: var(--sme-text-sm);
947
+ color: var(--sme-color-text);
948
+ }
949
+ .smeditor-track-panel__head {
950
+ display: flex;
951
+ justify-content: space-between;
952
+ align-items: center;
953
+ font-weight: 600;
954
+ }
955
+ .smeditor-track-panel__count {
956
+ background: var(--sme-color-accent-soft);
957
+ color: var(--sme-color-accent);
958
+ border-radius: 10px;
959
+ padding: 1px 8px;
960
+ font-size: 11px;
961
+ }
962
+ .smeditor-track-panel__empty {
963
+ color: var(--sme-color-text-muted);
964
+ margin: 0;
965
+ }
966
+ .smeditor-track-panel__list {
967
+ list-style: none;
968
+ margin: 0;
969
+ padding: 0;
970
+ display: flex;
971
+ flex-direction: column;
972
+ gap: 6px;
973
+ }
974
+ .smeditor-track-item {
975
+ border: 1px solid var(--sme-color-border);
976
+ border-radius: var(--sme-radius-sm);
977
+ padding: 6px 8px;
978
+ display: flex;
979
+ flex-direction: column;
980
+ gap: 4px;
981
+ }
982
+ .smeditor-track-item.is-insertion {
983
+ border-left: 3px solid #1a7f37;
984
+ }
985
+ .smeditor-track-item.is-deletion {
986
+ border-left: 3px solid #b35900;
987
+ }
988
+ .smeditor-track-item__meta {
989
+ display: flex;
990
+ gap: 6px;
991
+ font-size: 11px;
992
+ color: var(--sme-color-text-muted);
993
+ }
994
+ .smeditor-track-item__type {
995
+ font-weight: 600;
996
+ }
997
+ .smeditor-track-item__text {
998
+ font-family: var(--sme-font-mono, monospace);
999
+ word-break: break-word;
1000
+ }
1001
+ .smeditor-track-item__actions,
1002
+ .smeditor-track-panel__bulk {
1003
+ display: flex;
1004
+ gap: 6px;
1005
+ }
1006
+ .smeditor-track-item__actions button,
1007
+ .smeditor-track-panel__bulk button {
1008
+ flex: 1;
1009
+ appearance: none;
1010
+ border: 1px solid var(--sme-color-border);
1011
+ background: var(--sme-color-surface);
1012
+ color: var(--sme-color-text);
1013
+ font: inherit;
1014
+ font-size: 12px;
1015
+ padding: 3px 6px;
1016
+ border-radius: var(--sme-radius-sm);
1017
+ cursor: pointer;
1018
+ }
1019
+ .smeditor-track-item__actions button:hover,
1020
+ .smeditor-track-panel__bulk button:hover {
1021
+ background: var(--sme-color-accent-soft);
1022
+ }
1023
+
1024
+ /* Remote collaborator cursors */
1025
+ .smeditor-remote-cursors {
1026
+ position: absolute;
1027
+ inset: 0;
1028
+ pointer-events: none;
1029
+ overflow: hidden;
1030
+ }
1031
+ .smeditor-remote-cursor {
1032
+ position: absolute;
1033
+ width: 0;
1034
+ }
1035
+ .smeditor-remote-cursor__caret {
1036
+ position: absolute;
1037
+ top: 0;
1038
+ left: -1px;
1039
+ width: 2px;
1040
+ height: 100%;
1041
+ border-radius: 1px;
1042
+ }
1043
+ .smeditor-remote-cursor__label {
1044
+ position: absolute;
1045
+ top: -1.2em;
1046
+ left: -1px;
1047
+ font-size: 11px;
1048
+ line-height: 1.2;
1049
+ color: #fff;
1050
+ padding: 1px 5px;
1051
+ border-radius: 3px 3px 3px 0;
1052
+ white-space: nowrap;
1053
+ font-family: var(--sme-font-ui, system-ui, sans-serif);
1054
+ }
1055
+
1056
+ /* Iteration 12: default theme polish ------------------------------------ */
1057
+ .smeditor,
1058
+ .smeditor * {
1059
+ box-sizing: border-box;
1060
+ }
1061
+
1062
+ .smeditor:focus-within {
1063
+ border-color: var(--sme-color-focus);
1064
+ box-shadow: var(--sme-focus-ring);
1065
+ }
1066
+
1067
+ .smeditor-button {
1068
+ min-height: var(--sme-control-height);
1069
+ }
1070
+
1071
+ .smeditor-button:disabled:hover,
1072
+ .smeditor-button[aria-disabled="true"]:hover {
1073
+ background: transparent;
1074
+ }
1075
+
1076
+ .smeditor-button[aria-pressed="true"] {
1077
+ background: var(--sme-color-accent-soft);
1078
+ color: var(--sme-color-accent);
1079
+ }
1080
+
1081
+ .smeditor-dropdown__item:focus-visible,
1082
+ .smeditor-slash-item:focus-visible,
1083
+ .smeditor-mention-item:focus-visible,
1084
+ .smeditor-source-view__actions .smeditor-button:focus-visible,
1085
+ .smeditor-track-item__actions button:focus-visible,
1086
+ .smeditor-track-panel__bulk button:focus-visible {
1087
+ outline: 2px solid var(--sme-color-focus);
1088
+ outline-offset: 1px;
1089
+ }
1090
+
1091
+ .smeditor-dropdown__menu,
1092
+ .smeditor-bubble-menu,
1093
+ .smeditor-slash-menu,
1094
+ .smeditor-mention-menu,
1095
+ .smeditor-toolbar--context {
1096
+ box-shadow: var(--sme-shadow-md);
1097
+ }
1098
+
1099
+ .smeditor-dropdown__menu {
1100
+ max-width: min(320px, calc(100vw - 24px));
1101
+ max-height: min(420px, calc(100vh - 24px));
1102
+ overflow: auto;
1103
+ }
1104
+
1105
+ .smeditor-dropdown__item:disabled {
1106
+ color: var(--sme-color-text-muted);
1107
+ cursor: not-allowed;
1108
+ opacity: 0.55;
1109
+ }
1110
+
1111
+ .smeditor-dropdown__item:disabled:hover {
1112
+ background: transparent;
1113
+ }
1114
+
1115
+ .smeditor-color-grid__swatch:focus-visible,
1116
+ .smeditor-table-picker__cell:focus-visible,
1117
+ .smeditor-image-resizer__handle:focus-visible {
1118
+ outline: 2px solid var(--sme-color-focus);
1119
+ outline-offset: 2px;
1120
+ }
1121
+
1122
+ .smeditor-color-grid__swatch[aria-pressed="true"],
1123
+ .smeditor-color-grid__swatch.is-active {
1124
+ box-shadow: 0 0 0 2px var(--sme-color-surface),
1125
+ 0 0 0 4px var(--sme-color-focus);
1126
+ }
1127
+
1128
+ .smeditor-content,
1129
+ .smeditor-editor {
1130
+ overflow-wrap: anywhere;
1131
+ tab-size: 2;
1132
+ }
1133
+
1134
+ .smeditor-editor:focus-visible {
1135
+ outline: none;
1136
+ }
1137
+
1138
+ .smeditor-editor :where(img.smeditor-image):focus-visible {
1139
+ outline: 2px solid var(--sme-color-focus);
1140
+ outline-offset: 2px;
1141
+ }
1142
+
1143
+ .smeditor-editor :where(table.smeditor-table) {
1144
+ max-width: 100%;
1145
+ }
1146
+
1147
+ .smeditor-editor :where(table.smeditor-table td[data-align="center"]),
1148
+ .smeditor-editor :where(table.smeditor-table th[data-align="center"]) {
1149
+ text-align: center;
1150
+ }
1151
+
1152
+ .smeditor-editor :where(table.smeditor-table td[data-align="right"]),
1153
+ .smeditor-editor :where(table.smeditor-table th[data-align="right"]) {
1154
+ text-align: right;
1155
+ }
1156
+
1157
+ .smeditor-editor :where(a):focus-visible {
1158
+ outline: 2px solid var(--sme-color-focus);
1159
+ outline-offset: 2px;
1160
+ border-radius: 2px;
1161
+ }
1162
+
1163
+ .smeditor-editor :where(pre) {
1164
+ max-width: 100%;
1165
+ }
1166
+
1167
+ .smeditor-remote-cursor__caret {
1168
+ display: inline-block;
1169
+ width: 2px;
1170
+ min-height: 1.2em;
1171
+ background: var(--sme-color-accent);
1172
+ }
1173
+
1174
+ .smeditor-remote-cursor__label {
1175
+ display: inline-block;
1176
+ margin-left: 2px;
1177
+ padding: 1px 5px;
1178
+ border-radius: var(--sme-radius-sm);
1179
+ background: var(--sme-color-accent);
1180
+ color: var(--sme-color-surface);
1181
+ font-size: 11px;
1182
+ }
1183
+
1184
+ @media (max-width: 640px) {
1185
+ .smeditor-toolbar {
1186
+ gap: 4px;
1187
+ padding: var(--sme-space-2);
1188
+ }
1189
+
1190
+ .smeditor-toolbar__divider {
1191
+ width: 100%;
1192
+ height: 1px;
1193
+ margin: var(--sme-space-1) 0;
1194
+ }
1195
+
1196
+ .smeditor-button {
1197
+ min-width: var(--sme-control-height);
1198
+ padding-inline: 7px;
1199
+ }
1200
+
1201
+ .smeditor-dropdown__menu.is-right {
1202
+ left: 0;
1203
+ right: auto;
1204
+ }
1205
+
1206
+ .smeditor-content,
1207
+ .smeditor-editor {
1208
+ padding: var(--sme-space-3);
1209
+ }
1210
+ }
1211
+
1212
+
1213
+ /* Rails self-contained adapter controls */
1214
+ .smeditor-select { height: 30px; max-width: 130px; border: 1px solid var(--sme-color-border); border-radius: var(--sme-radius-sm); background: var(--sme-color-surface); color: var(--sme-color-text); padding: 0 6px; font: inherit; font-size: var(--sme-text-sm); }
1215
+ .smeditor-select:focus-visible, .smeditor-color-input:focus-visible { outline: 2px solid var(--sme-color-accent); outline-offset: 1px; }
1216
+ .smeditor-color-input { width: 30px; height: 30px; padding: 2px; border: 1px solid var(--sme-color-border); border-radius: var(--sme-radius-sm); background: var(--sme-color-surface); cursor: pointer; }
1217
+ .smeditor-file-input { display: none !important; }
1218
+ .smeditor-mount { width: 100%; }