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