@rathkit/rte-ui 0.1.0-alpha.3 → 0.1.0-alpha.5

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.
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @rathkit/rte-ui
2
2
 
3
- > **Alpha** (0.1.0-alpha.0). Not for production: the APIs will change between releases. Published to the `next` tag only.
3
+ > **Alpha.** Not for production: the APIs will change between releases. Published to the `next` tag only.
4
4
 
5
- The styled interface: a toolbar, menus, dialogs, find bar, status line and tooltips on the headless layer, themeable by preset, tokens, icons, classes and messages.
5
+ The styled interface: a toolbar, menus, dialogs, find bar, status line and tooltips on the headless layer, with an optional selection toolbar and context menu, themeable by preset, tokens, icons, classes and messages.
6
6
 
7
7
  ## Install
8
8
 
package/css/base.css CHANGED
@@ -1,264 +1,272 @@
1
1
  /* @rathkit/rte-ui base styles. Everything is in cascade layers, so any CSS you write outside a layer wins without a specificity fight.
2
- Parts are addressed by data-rk-part, never by class; the only class is .rk-root, which scopes everything to one editor.
3
- Colors, sizes and fonts come from the --rk-* tokens (see @rathkit/rte-themes). Nothing here reaches outside .rk-root. */
2
+ Parts are addressed by data-rte-part, never by class; the only class is .rte-root, which scopes everything to one editor.
3
+ Colors, sizes and fonts come from the --rte-* tokens (see @rathkit/rte-themes). Nothing here reaches outside .rte-root. */
4
4
  @layer rathkit.defaults, rathkit.base;
5
5
 
6
6
  /* Light values, used only when no theme is chosen, so the editor is never unstyled. A theme's tokens replace them. */
7
7
  @layer rathkit.defaults {
8
- .rk-root:where(:not([data-rk-theme])) {
9
- --rk-bg: #ffffff; --rk-surface: #f4f6f9; --rk-surface-raised: #ffffff; --rk-fg: #14181f; --rk-fg-muted: #505b6b;
10
- --rk-border: #78849a; --rk-border-subtle: #d4dae3; --rk-accent: #17408b; --rk-accent-fg: #ffffff; --rk-accent-subtle: #e3ecfa;
11
- --rk-danger: #b3261e; --rk-selection-bg: #cddffa; --rk-control-hover: #e6ebf3; --rk-control-active: #d3dcec;
12
- --rk-brand: #b9812a; --rk-brand-text: #86590f; --rk-focus-color: #17408b;
13
- --rk-space-1: 4px; --rk-space-2: 8px; --rk-space-3: 12px; --rk-space-4: 16px; --rk-control-size: 32px;
14
- --rk-radius-s: 4px; --rk-radius-m: 8px; --rk-border-width: 1px; --rk-focus-width: 2px; --rk-focus-offset: 2px;
15
- --rk-font-ui: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; --rk-font-content: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
16
- --rk-font-heading: system-ui, sans-serif; --rk-font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
17
- --rk-font-size: 16px; --rk-line-height: 1.55; --rk-motion-duration: 120ms;
18
- --rk-shadow-popover: 0 6px 24px rgb(20 24 31 / 0.18), 0 1px 3px rgb(20 24 31 / 0.12);
8
+ .rte-root:where(:not([data-rte-theme])) {
9
+ --rte-bg: #ffffff; --rte-surface: #f4f6f9; --rte-surface-raised: #ffffff; --rte-fg: #14181f; --rte-fg-muted: #505b6b;
10
+ --rte-border: #78849a; --rte-border-subtle: #d4dae3; --rte-accent: #17408b; --rte-accent-fg: #ffffff; --rte-accent-subtle: #e3ecfa;
11
+ --rte-danger: #b3261e; --rte-selection-bg: #cddffa; --rte-control-hover: #e6ebf3; --rte-control-active: #d3dcec;
12
+ --rte-brand: #b9812a; --rte-brand-text: #86590f; --rte-focus-color: #17408b;
13
+ --rte-space-1: 4px; --rte-space-2: 8px; --rte-space-3: 12px; --rte-space-4: 16px; --rte-control-size: 32px;
14
+ --rte-radius-s: 4px; --rte-radius-m: 8px; --rte-border-width: 1px; --rte-focus-width: 2px; --rte-focus-offset: 2px;
15
+ --rte-font-ui: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; --rte-font-content: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
16
+ --rte-font-heading: system-ui, sans-serif; --rte-font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
17
+ --rte-font-size: 16px; --rte-line-height: 1.55; --rte-motion-duration: 120ms;
18
+ --rte-shadow-popover: 0 6px 24px rgb(20 24 31 / 0.18), 0 1px 3px rgb(20 24 31 / 0.12);
19
19
  color-scheme: light;
20
20
  }
21
21
  }
22
22
 
23
23
  @layer rathkit.base {
24
- .rk-root {
24
+ .rte-root {
25
25
  box-sizing: border-box;
26
26
  position: relative;
27
27
  display: flex;
28
28
  flex-direction: column;
29
- font: var(--rk-font-size) / var(--rk-line-height) var(--rk-font-ui);
30
- color: var(--rk-fg);
31
- background: var(--rk-bg);
32
- border: var(--rk-border-width) solid var(--rk-border);
33
- border-radius: var(--rk-radius-m);
29
+ font: var(--rte-font-size) / var(--rte-line-height) var(--rte-font-ui);
30
+ color: var(--rte-fg);
31
+ background: var(--rte-bg);
32
+ border: var(--rte-border-width) solid var(--rte-border);
33
+ border-radius: var(--rte-radius-m);
34
34
  }
35
- .rk-root *, .rk-root *::before, .rk-root *::after { box-sizing: inherit; }
35
+ .rte-root *, .rte-root *::before, .rte-root *::after { box-sizing: inherit; }
36
36
 
37
37
  /* ---- toolbar ---- */
38
- [data-rk-part="toolbar"] {
38
+ [data-rte-part="toolbar"] {
39
39
  display: flex;
40
40
  flex-wrap: wrap;
41
41
  align-items: center;
42
- gap: var(--rk-space-1);
43
- padding: var(--rk-space-2);
44
- background: var(--rk-surface);
45
- border-bottom: var(--rk-border-width) solid var(--rk-border);
46
- border-radius: calc(var(--rk-radius-m) - var(--rk-border-width)) calc(var(--rk-radius-m) - var(--rk-border-width)) 0 0;
42
+ gap: var(--rte-space-1);
43
+ padding: var(--rte-space-2);
44
+ background: var(--rte-surface);
45
+ border-bottom: var(--rte-border-width) solid var(--rte-border);
46
+ border-radius: calc(var(--rte-radius-m) - var(--rte-border-width)) calc(var(--rte-radius-m) - var(--rte-border-width)) 0 0;
47
47
  }
48
- [data-rk-part="separator"] {
48
+ [data-rte-part="separator"] {
49
49
  align-self: stretch;
50
- width: var(--rk-border-width);
51
- margin-inline: var(--rk-space-1);
52
- background: var(--rk-border-subtle);
50
+ width: var(--rte-border-width);
51
+ margin-inline: var(--rte-space-1);
52
+ background: var(--rte-border-subtle);
53
53
  }
54
- [data-rk-part="button"],
55
- [data-rk-part="select"] {
56
- min-width: var(--rk-control-size);
57
- height: var(--rk-control-size);
54
+ [data-rte-part="button"],
55
+ [data-rte-part="select"] {
56
+ min-width: var(--rte-control-size);
57
+ height: var(--rte-control-size);
58
58
  margin: 0;
59
- padding: 0 var(--rk-space-2);
59
+ padding: 0 var(--rte-space-2);
60
60
  font: inherit;
61
- color: var(--rk-fg);
61
+ color: var(--rte-fg);
62
62
  background: transparent;
63
- border: var(--rk-border-width) solid transparent;
64
- border-radius: var(--rk-radius-s);
63
+ border: var(--rte-border-width) solid transparent;
64
+ border-radius: var(--rte-radius-s);
65
65
  cursor: pointer;
66
- transition: background-color calc(var(--rk-motion-duration) * var(--rk-motion-scale, 1)) ease;
66
+ transition: background-color calc(var(--rte-motion-duration) * var(--rte-motion-scale, 1)) ease;
67
67
  }
68
- [data-rk-part="button"] { display: inline-flex; align-items: center; justify-content: center; gap: var(--rk-space-1); }
69
- [data-rk-part="select"] { background: var(--rk-surface-raised); border-color: var(--rk-border); }
70
- [data-rk-part="button"]:hover:not([aria-disabled="true"]),
71
- [data-rk-part="select"]:hover:not([aria-disabled="true"]) { background: var(--rk-control-hover); }
72
- [data-rk-part="button"]:active:not([aria-disabled="true"]) { background: var(--rk-control-active); }
68
+ [data-rte-part="button"] { display: inline-flex; align-items: center; justify-content: center; gap: var(--rte-space-1); }
69
+ [data-rte-part="select"] { background: var(--rte-surface-raised); border-color: var(--rte-border); }
70
+ [data-rte-part="button"]:hover:not([aria-disabled="true"]),
71
+ [data-rte-part="select"]:hover:not([aria-disabled="true"]) { background: var(--rte-control-hover); }
72
+ [data-rte-part="button"]:active:not([aria-disabled="true"]) { background: var(--rte-control-active); }
73
73
  /* On: a tint, an accent border all round, and a thicker bar underneath, so it never depends on color alone. */
74
- [data-rk-part="button"][aria-pressed="true"] {
75
- color: var(--rk-accent);
76
- background: var(--rk-accent-subtle);
77
- border-color: var(--rk-accent);
74
+ [data-rte-part="button"][aria-pressed="true"] {
75
+ color: var(--rte-accent);
76
+ background: var(--rte-accent-subtle);
77
+ border-color: var(--rte-accent);
78
78
  border-bottom-width: 3px;
79
79
  }
80
- [data-rk-part="button"][aria-disabled="true"],
81
- [data-rk-part="select"][aria-disabled="true"] { opacity: 0.45; cursor: not-allowed; }
82
- [data-rk-part="icon"] { display: inline-flex; width: 20px; height: 20px; }
83
- [data-rk-part="icon"] svg { width: 100%; height: 100%; }
80
+ [data-rte-part="button"][aria-disabled="true"],
81
+ [data-rte-part="select"][aria-disabled="true"] { opacity: 0.45; cursor: not-allowed; }
82
+ [data-rte-part="icon"] { display: inline-flex; width: 20px; height: 20px; }
83
+ [data-rte-part="icon"] svg { width: 100%; height: 100%; }
84
84
 
85
85
  /* ---- menus: a button that opens a list of commands ---- */
86
- [data-rk-part="menu-host"] { position: relative; display: inline-flex; }
87
- [data-rk-part="menu"] {
88
- position: absolute; z-index: 10; inset-block-start: calc(100% + var(--rk-space-1)); inset-inline-start: 0;
89
- min-width: 14rem; padding: var(--rk-space-1);
90
- background: var(--rk-surface-raised); border: var(--rk-border-width) solid var(--rk-border); border-radius: var(--rk-radius-m);
91
- box-shadow: var(--rk-shadow-popover); max-height: min(22rem, 60vh); overflow-y: auto;
86
+ [data-rte-part="menu-host"] { position: relative; display: inline-flex; }
87
+ [data-rte-part="menu"] {
88
+ position: absolute; z-index: 10; inset-block-start: calc(100% + var(--rte-space-1)); inset-inline-start: 0;
89
+ min-width: 14rem; padding: var(--rte-space-1);
90
+ background: var(--rte-surface-raised); border: var(--rte-border-width) solid var(--rte-border); border-radius: var(--rte-radius-m);
91
+ box-shadow: var(--rte-shadow-popover); max-height: min(22rem, 60vh); overflow-y: auto;
92
92
  }
93
- [data-rk-part="menu"][hidden] { display: none; }
94
- [data-rk-part="menu-item"] {
95
- display: flex; align-items: center; justify-content: space-between; gap: var(--rk-space-4); width: 100%; min-height: var(--rk-control-size);
96
- margin: 0; padding: 0 var(--rk-space-2) 0 calc(var(--rk-space-2) + 1.4em); font: inherit; text-align: start; color: var(--rk-fg);
97
- background: transparent; border: 0; border-radius: var(--rk-radius-s); cursor: pointer; position: relative;
93
+ [data-rte-part="menu"][hidden] { display: none; }
94
+ [data-rte-part="menu-item"] {
95
+ display: flex; align-items: center; justify-content: space-between; gap: var(--rte-space-4); width: 100%; min-height: var(--rte-control-size);
96
+ margin: 0; padding: 0 var(--rte-space-2) 0 calc(var(--rte-space-2) + 1.4em); font: inherit; text-align: start; color: var(--rte-fg);
97
+ background: transparent; border: 0; border-radius: var(--rte-radius-s); cursor: pointer; position: relative;
98
98
  }
99
- [data-rk-part="menu-item"]:hover:not([aria-disabled="true"]), [data-rk-part="menu-item"]:focus-visible { background: var(--rk-control-hover); }
100
- [data-rk-part="menu-item"][aria-disabled="true"] { opacity: 0.45; cursor: not-allowed; }
99
+ [data-rte-part="menu-item"]:hover:not([aria-disabled="true"]), [data-rte-part="menu-item"]:focus-visible { background: var(--rte-control-hover); }
100
+ [data-rte-part="menu-item"][aria-disabled="true"] { opacity: 0.45; cursor: not-allowed; }
101
101
  /* A switched-on entry has a check mark in front of it, so it never depends on color alone. */
102
- [data-rk-part="menu-item"][aria-checked="true"]::before { content: "\2713"; position: absolute; inset-inline-start: var(--rk-space-2); font-weight: 700; color: var(--rk-accent); }
102
+ [data-rte-part="menu-item"][aria-checked="true"]::before { content: "\2713"; position: absolute; inset-inline-start: var(--rte-space-2); font-weight: 700; color: var(--rte-accent); }
103
103
  /* A sample of a color, with a border so it shows on any background (a white swatch on white, say). */
104
- [data-rk-part="swatch"] { flex: none; width: 1em; height: 1em; margin-inline-end: var(--rk-space-2); border: var(--rk-border-width) solid var(--rk-border); border-radius: var(--rk-radius-s); }
105
- [data-rk-part="menu-item"]:has([data-rk-part="swatch"]) { justify-content: flex-start; }
106
- [data-rk-part="menu-item"]:has([data-rk-part="swatch"]) [data-rk-part="menu-keys"] { margin-inline-start: auto; }
107
- [data-rk-part="menu-keys"] { color: var(--rk-fg-muted); font-size: 0.85em; }
108
- [data-rk-part="menu-separator"] { height: var(--rk-border-width); margin: var(--rk-space-1) var(--rk-space-2); background: var(--rk-border-subtle); }
104
+ [data-rte-part="swatch"] { flex: none; width: 1em; height: 1em; margin-inline-end: var(--rte-space-2); border: var(--rte-border-width) solid var(--rte-border); border-radius: var(--rte-radius-s); }
105
+ [data-rte-part="menu-item"]:has([data-rte-part="swatch"]) { justify-content: flex-start; }
106
+ [data-rte-part="menu-item"]:has([data-rte-part="swatch"]) [data-rte-part="menu-keys"] { margin-inline-start: auto; }
107
+ [data-rte-part="menu-keys"] { color: var(--rte-fg-muted); font-size: 0.85em; }
108
+ [data-rte-part="menu-separator"] { height: var(--rte-border-width); margin: var(--rte-space-1) var(--rte-space-2); background: var(--rte-border-subtle); }
109
+
110
+ /* ---- the selection toolbar: a short row of controls over selected text ---- */
111
+ [data-rte-part="floating"] {
112
+ position: absolute; z-index: 15; display: flex; align-items: center; gap: var(--rte-space-1); padding: var(--rte-space-1);
113
+ background: var(--rte-surface-raised); border: var(--rte-border-width) solid var(--rte-border); border-radius: var(--rte-radius-m); box-shadow: var(--rte-shadow-popover);
114
+ }
115
+ [data-rte-part="floating"][hidden] { display: none; }
116
+ [data-rte-part="floating"] [data-rte-part="separator"] { align-self: stretch; margin-block: var(--rte-space-1); }
109
117
 
110
118
  /* ---- tooltip: the control's name and key, shown near it on hover and keyboard focus ---- */
111
- [data-rk-part="tooltip"] {
112
- position: absolute; z-index: 20; max-width: 20rem; padding: var(--rk-space-1) var(--rk-space-2); font-size: 0.85em; line-height: 1.35;
113
- color: var(--rk-bg); background: var(--rk-fg); border-radius: var(--rk-radius-s); box-shadow: var(--rk-shadow-popover);
119
+ [data-rte-part="tooltip"] {
120
+ position: absolute; z-index: 20; max-width: 20rem; padding: var(--rte-space-1) var(--rte-space-2); font-size: 0.85em; line-height: 1.35;
121
+ color: var(--rte-bg); background: var(--rte-fg); border-radius: var(--rte-radius-s); box-shadow: var(--rte-shadow-popover);
114
122
  white-space: nowrap; pointer-events: auto;
115
123
  }
116
- [data-rk-part="tooltip"][hidden] { display: none; }
124
+ [data-rte-part="tooltip"][hidden] { display: none; }
117
125
 
118
- .rk-root :focus-visible {
119
- outline: var(--rk-focus-width) solid var(--rk-focus-color);
120
- outline-offset: var(--rk-focus-offset);
126
+ .rte-root :focus-visible {
127
+ outline: var(--rte-focus-width) solid var(--rte-focus-color);
128
+ outline-offset: var(--rte-focus-offset);
121
129
  }
122
130
 
123
131
  /* ---- the writing area ---- */
124
- [data-rk-part="content"] {
125
- padding: var(--rk-space-3) var(--rk-space-4);
126
- background: var(--rk-bg);
127
- border-radius: 0 0 var(--rk-radius-m) var(--rk-radius-m);
128
- font-family: var(--rk-font-content);
132
+ [data-rte-part="content"] {
133
+ padding: var(--rte-space-3) var(--rte-space-4);
134
+ background: var(--rte-bg);
135
+ border-radius: 0 0 var(--rte-radius-m) var(--rte-radius-m);
136
+ font-family: var(--rte-font-content);
129
137
  }
130
- [data-rk-part="content"] > :first-child { outline: none; min-height: 8em; }
131
- [data-rk-part="content"] ::selection { background: var(--rk-selection-bg); color: var(--rk-fg); }
132
- /* The hint shown while the document is empty (the editor sets data-rk-empty and --rk-placeholder). */
133
- [data-rk-part="content"] > [data-rk-empty] > :first-child::before { content: var(--rk-placeholder, none); color: var(--rk-fg-muted); pointer-events: none; }
134
- [data-rk-part="content"] > :first-child > :first-child { margin-top: 0; }
135
- [data-rk-part="content"] > :first-child > :last-child { margin-bottom: 0; }
136
- [data-rk-part="content"] p { margin: 0 0 0.75em; }
137
- [data-rk-part="content"] :is(h1, h2, h3, h4, h5, h6) { margin: 1.1em 0 0.4em; font-family: var(--rk-font-heading); line-height: 1.25; font-weight: 700; }
138
- [data-rk-part="content"] h1 { font-size: 1.9em; }
139
- [data-rk-part="content"] h2 { font-size: 1.5em; }
140
- [data-rk-part="content"] h3 { font-size: 1.25em; }
141
- [data-rk-part="content"] h4 { font-size: 1.1em; }
142
- [data-rk-part="content"] :is(h5, h6) { font-size: 1em; }
143
- [data-rk-part="content"] :is(ul, ol) { margin: 0 0 0.75em; padding-inline-start: 1.6em; }
144
- [data-rk-part="content"] :is(ul, ol) :is(ul, ol) { margin-bottom: 0; }
138
+ [data-rte-part="content"] > :first-child { outline: none; min-height: 8em; }
139
+ [data-rte-part="content"] ::selection { background: var(--rte-selection-bg); color: var(--rte-fg); }
140
+ /* The hint shown while the document is empty (the editor sets data-rte-empty and --rte-placeholder). */
141
+ [data-rte-part="content"] > [data-rte-empty] > :first-child::before { content: var(--rte-placeholder, none); color: var(--rte-fg-muted); pointer-events: none; }
142
+ [data-rte-part="content"] > :first-child > :first-child { margin-top: 0; }
143
+ [data-rte-part="content"] > :first-child > :last-child { margin-bottom: 0; }
144
+ [data-rte-part="content"] p { margin: 0 0 0.75em; }
145
+ [data-rte-part="content"] :is(h1, h2, h3, h4, h5, h6) { margin: 1.1em 0 0.4em; font-family: var(--rte-font-heading); line-height: 1.25; font-weight: 700; }
146
+ [data-rte-part="content"] h1 { font-size: 1.9em; }
147
+ [data-rte-part="content"] h2 { font-size: 1.5em; }
148
+ [data-rte-part="content"] h3 { font-size: 1.25em; }
149
+ [data-rte-part="content"] h4 { font-size: 1.1em; }
150
+ [data-rte-part="content"] :is(h5, h6) { font-size: 1em; }
151
+ [data-rte-part="content"] :is(ul, ol) { margin: 0 0 0.75em; padding-inline-start: 1.6em; }
152
+ [data-rte-part="content"] :is(ul, ol) :is(ul, ol) { margin-bottom: 0; }
145
153
  /* A task's checkbox is a glyph in front of its text; clicking it ticks the task. */
146
- [data-rk-part="content"] li[data-rk-task] { list-style: none; }
147
- [data-rk-part="content"] [data-rte-check] { display: inline-block; margin-inline-end: 0.4em; cursor: pointer; user-select: none; font-size: 1.1em; line-height: 1; color: var(--rk-accent); }
148
- [data-rk-part="content"] li[data-rk-task="open"] > [data-rte-check] { color: var(--rk-fg-muted); }
149
- [data-rk-part="content"] dt { font-weight: 700; }
150
- [data-rk-part="content"] dd { margin-inline-start: 1.6em; }
151
- [data-rk-part="content"] blockquote { margin: 0 0 0.75em; padding-inline-start: var(--rk-space-3); border-inline-start: 3px solid var(--rk-brand); color: var(--rk-fg-muted); }
152
- [data-rk-part="content"] pre { margin: 0 0 0.75em; padding: var(--rk-space-2) var(--rk-space-3); font-family: var(--rk-font-mono); font-size: 0.9em; background: var(--rk-surface); border: var(--rk-border-width) solid var(--rk-border-subtle); border-radius: var(--rk-radius-s); overflow-x: auto; }
153
- [data-rk-part="content"] code { font-family: var(--rk-font-mono); font-size: 0.9em; padding: 0.1em 0.3em; background: var(--rk-surface); border-radius: var(--rk-radius-s); }
154
- [data-rk-part="content"] pre code { padding: 0; background: none; }
155
- [data-rk-part="content"] a { color: var(--rk-accent); text-decoration: underline; text-underline-offset: 0.15em; }
156
- [data-rk-part="content"] hr { border: 0; border-top: var(--rk-border-width) solid var(--rk-border); margin: 1em 0; }
157
- [data-rk-part="content"] img { max-width: 100%; height: auto; }
154
+ [data-rte-part="content"] li[data-rte-task] { list-style: none; }
155
+ [data-rte-part="content"] [data-rte-check] { display: inline-block; margin-inline-end: 0.4em; cursor: pointer; user-select: none; font-size: 1.1em; line-height: 1; color: var(--rte-accent); }
156
+ [data-rte-part="content"] li[data-rte-task="open"] > [data-rte-check] { color: var(--rte-fg-muted); }
157
+ [data-rte-part="content"] dt { font-weight: 700; }
158
+ [data-rte-part="content"] dd { margin-inline-start: 1.6em; }
159
+ [data-rte-part="content"] blockquote { margin: 0 0 0.75em; padding-inline-start: var(--rte-space-3); border-inline-start: 3px solid var(--rte-brand); color: var(--rte-fg-muted); }
160
+ [data-rte-part="content"] pre { margin: 0 0 0.75em; padding: var(--rte-space-2) var(--rte-space-3); font-family: var(--rte-font-mono); font-size: 0.9em; background: var(--rte-surface); border: var(--rte-border-width) solid var(--rte-border-subtle); border-radius: var(--rte-radius-s); overflow-x: auto; }
161
+ [data-rte-part="content"] code { font-family: var(--rte-font-mono); font-size: 0.9em; padding: 0.1em 0.3em; background: var(--rte-surface); border-radius: var(--rte-radius-s); }
162
+ [data-rte-part="content"] pre code { padding: 0; background: none; }
163
+ [data-rte-part="content"] a { color: var(--rte-accent); text-decoration: underline; text-underline-offset: 0.15em; }
164
+ [data-rte-part="content"] hr { border: 0; border-top: var(--rte-border-width) solid var(--rte-border); margin: 1em 0; }
165
+ [data-rte-part="content"] img { max-width: 100%; height: auto; }
158
166
  /* An image from another site that the host has not allowed to load: a dashed box in its place, so it is plain that something is there. */
159
- [data-rk-part="content"] img[data-rte-remote-src] { display: inline-block; min-width: 6em; min-height: 3em; vertical-align: middle; border: 2px dashed var(--rk-border); border-radius: var(--rk-radius-s); background: var(--rk-surface); }
160
- [data-rk-part="content"] table { border-collapse: collapse; margin: 0 0 0.75em; }
161
- [data-rk-part="content"] :is(td, th) { padding: var(--rk-space-1) var(--rk-space-2); border: var(--rk-border-width) solid var(--rk-border); vertical-align: top; min-width: 2em; }
162
- [data-rk-part="content"] :is(td, th) > p { margin: 0; }
163
- [data-rk-part="content"] :is(td, th) > p + p { margin-top: 0.5em; }
164
- [data-rk-part="content"] th { background: var(--rk-surface); text-align: start; }
167
+ [data-rte-part="content"] img[data-rte-remote-src] { display: inline-block; min-width: 6em; min-height: 3em; vertical-align: middle; border: 2px dashed var(--rte-border); border-radius: var(--rte-radius-s); background: var(--rte-surface); }
168
+ [data-rte-part="content"] table { border-collapse: collapse; margin: 0 0 0.75em; }
169
+ [data-rte-part="content"] :is(td, th) { padding: var(--rte-space-1) var(--rte-space-2); border: var(--rte-border-width) solid var(--rte-border); vertical-align: top; min-width: 2em; }
170
+ [data-rte-part="content"] :is(td, th) > p { margin: 0; }
171
+ [data-rte-part="content"] :is(td, th) > p + p { margin-top: 0.5em; }
172
+ [data-rte-part="content"] th { background: var(--rte-surface); text-align: start; }
165
173
  /* Cells a selection runs across (the view marks them): a tint and an outline, so it is not color alone. */
166
- [data-rk-part="content"] [data-rk-cell-selected] { background: var(--rk-selection-bg); outline: 2px solid var(--rk-accent); outline-offset: -2px; }
167
- [data-rk-part="content"] img[data-rk-image-selected] { outline: 2px solid var(--rk-accent); outline-offset: 2px; }
174
+ [data-rte-part="content"] [data-rte-cell-selected] { background: var(--rte-selection-bg); outline: 2px solid var(--rte-accent); outline-offset: -2px; }
175
+ [data-rte-part="content"] img[data-rte-image-selected] { outline: 2px solid var(--rte-accent); outline-offset: 2px; }
168
176
  /* Focus inside the writing area shows on the whole editor, not as a box around the text. */
169
- .rk-root:focus-within { outline: var(--rk-focus-width) solid var(--rk-focus-color); outline-offset: var(--rk-focus-offset); }
170
- .rk-root:focus-within [data-rk-part="content"] :focus-visible,
171
- .rk-root [data-rk-part="content"] :focus-visible { outline: none; }
177
+ .rte-root:focus-within { outline: var(--rte-focus-width) solid var(--rte-focus-color); outline-offset: var(--rte-focus-offset); }
178
+ .rte-root:focus-within [data-rte-part="content"] :focus-visible,
179
+ .rte-root [data-rte-part="content"] :focus-visible { outline: none; }
172
180
 
173
181
  /* ---- HTML source view: the text box takes the place of the writing area ---- */
174
- [data-rk-part="content"][data-rk-source] > :not([data-rk-part="source"]) { display: none; }
175
- [data-rk-part="source"] {
176
- display: block; width: 100%; min-height: 14em; margin: 0; padding: 0; font: 0.9em / 1.5 var(--rk-font-mono); color: var(--rk-fg);
177
- background: var(--rk-bg); border: 0; outline: none; resize: vertical; tab-size: 2;
182
+ [data-rte-part="content"][data-rte-source] > :not([data-rte-part="source"]) { display: none; }
183
+ [data-rte-part="source"] {
184
+ display: block; width: 100%; min-height: 14em; margin: 0; padding: 0; font: 0.9em / 1.5 var(--rte-font-mono); color: var(--rte-fg);
185
+ background: var(--rte-bg); border: 0; outline: none; resize: vertical; tab-size: 2;
178
186
  }
179
- [data-rk-part="source"][hidden] { display: none; }
187
+ [data-rte-part="source"][hidden] { display: none; }
180
188
 
181
189
  /* ---- keyboard shortcuts list ---- */
182
- [data-rk-part="shortcuts"] { width: 100%; border-collapse: collapse; margin-bottom: var(--rk-space-2); }
183
- [data-rk-part="shortcuts"] :is(th, td) { padding: var(--rk-space-1) var(--rk-space-2); text-align: start; border-bottom: var(--rk-border-width) solid var(--rk-border-subtle); }
184
- [data-rk-part="shortcuts"] kbd { font: 0.9em var(--rk-font-mono); padding: 0.1em 0.4em; background: var(--rk-surface); border: var(--rk-border-width) solid var(--rk-border); border-radius: var(--rk-radius-s); }
190
+ [data-rte-part="shortcuts"] { width: 100%; border-collapse: collapse; margin-bottom: var(--rte-space-2); }
191
+ [data-rte-part="shortcuts"] :is(th, td) { padding: var(--rte-space-1) var(--rte-space-2); text-align: start; border-bottom: var(--rte-border-width) solid var(--rte-border-subtle); }
192
+ [data-rte-part="shortcuts"] kbd { font: 0.9em var(--rte-font-mono); padding: 0.1em 0.4em; background: var(--rte-surface); border: var(--rte-border-width) solid var(--rte-border); border-radius: var(--rte-radius-s); }
185
193
 
186
194
  /* ---- find and replace ---- */
187
- [data-rk-part="findbar"] { padding: var(--rk-space-2); background: var(--rk-surface); border-bottom: var(--rk-border-width) solid var(--rk-border); }
188
- [data-rk-part="findbar"][hidden] { display: none; }
189
- [data-rk-part="find-line"] { display: flex; flex-wrap: wrap; align-items: center; gap: var(--rk-space-1); }
190
- [data-rk-part="find-line"] + [data-rk-part="find-line"] { margin-top: var(--rk-space-1); }
191
- [data-rk-part="find-row"] { display: flex; align-items: center; gap: var(--rk-space-2); flex: 1 1 14rem; }
192
- [data-rk-part="find-label"] { min-width: 6.5em; font-weight: 600; }
193
- [data-rk-part="find-field"] {
194
- flex: 1; min-width: 6em; height: var(--rk-control-size); padding: 0 var(--rk-space-2); font: inherit; color: var(--rk-fg);
195
- background: var(--rk-bg); border: var(--rk-border-width) solid var(--rk-border); border-radius: var(--rk-radius-s);
195
+ [data-rte-part="findbar"] { padding: var(--rte-space-2); background: var(--rte-surface); border-bottom: var(--rte-border-width) solid var(--rte-border); }
196
+ [data-rte-part="findbar"][hidden] { display: none; }
197
+ [data-rte-part="find-line"] { display: flex; flex-wrap: wrap; align-items: center; gap: var(--rte-space-1); }
198
+ [data-rte-part="find-line"] + [data-rte-part="find-line"] { margin-top: var(--rte-space-1); }
199
+ [data-rte-part="find-row"] { display: flex; align-items: center; gap: var(--rte-space-2); flex: 1 1 14rem; }
200
+ [data-rte-part="find-label"] { min-width: 6.5em; font-weight: 600; }
201
+ [data-rte-part="find-field"] {
202
+ flex: 1; min-width: 6em; height: var(--rte-control-size); padding: 0 var(--rte-space-2); font: inherit; color: var(--rte-fg);
203
+ background: var(--rte-bg); border: var(--rte-border-width) solid var(--rte-border); border-radius: var(--rte-radius-s);
196
204
  }
197
- [data-rk-part="find-count"] { min-width: 5.5em; color: var(--rk-fg-muted); font-size: 0.9em; }
198
- [data-rk-part="findbar"] [aria-disabled="true"] { opacity: 0.45; cursor: not-allowed; }
205
+ [data-rte-part="find-count"] { min-width: 5.5em; color: var(--rte-fg-muted); font-size: 0.9em; }
206
+ [data-rte-part="findbar"] [aria-disabled="true"] { opacity: 0.45; cursor: not-allowed; }
199
207
 
200
208
  /* ---- status line ---- */
201
- [data-rk-part="status"] {
202
- padding: var(--rk-space-1) var(--rk-space-3); font-size: 0.85em; color: var(--rk-fg-muted); background: var(--rk-surface);
203
- border-top: var(--rk-border-width) solid var(--rk-border-subtle); border-radius: 0 0 calc(var(--rk-radius-m) - var(--rk-border-width)) calc(var(--rk-radius-m) - var(--rk-border-width));
209
+ [data-rte-part="status"] {
210
+ padding: var(--rte-space-1) var(--rte-space-3); font-size: 0.85em; color: var(--rte-fg-muted); background: var(--rte-surface);
211
+ border-top: var(--rte-border-width) solid var(--rte-border-subtle); border-radius: 0 0 calc(var(--rte-radius-m) - var(--rte-border-width)) calc(var(--rte-radius-m) - var(--rte-border-width));
204
212
  }
205
- [data-rk-part="content"]:has(+ [data-rk-part="status"]) { border-radius: 0; }
213
+ [data-rte-part="content"]:has(+ [data-rte-part="status"]) { border-radius: 0; }
206
214
 
207
215
  /* ---- dialog ---- */
208
- [data-rk-part="dialog"] {
209
- width: min(26rem, calc(100vw - 2 * var(--rk-space-4)));
210
- padding: var(--rk-space-4);
211
- color: var(--rk-fg);
212
- background: var(--rk-surface-raised);
213
- border: var(--rk-border-width) solid var(--rk-border);
214
- border-radius: var(--rk-radius-m);
215
- box-shadow: var(--rk-shadow-popover);
216
- font: var(--rk-font-size) / var(--rk-line-height) var(--rk-font-ui);
216
+ [data-rte-part="dialog"] {
217
+ width: min(26rem, calc(100vw - 2 * var(--rte-space-4)));
218
+ padding: var(--rte-space-4);
219
+ color: var(--rte-fg);
220
+ background: var(--rte-surface-raised);
221
+ border: var(--rte-border-width) solid var(--rte-border);
222
+ border-radius: var(--rte-radius-m);
223
+ box-shadow: var(--rte-shadow-popover);
224
+ font: var(--rte-font-size) / var(--rte-line-height) var(--rte-font-ui);
217
225
  }
218
- [data-rk-part="dialog"]::backdrop { background: rgb(0 0 0 / 0.45); }
219
- [data-rk-part="dialog-title"] { margin: 0 0 var(--rk-space-3); font: 700 1.15em / 1.3 var(--rk-font-heading); }
220
- [data-rk-part="field-label"] { display: block; margin-bottom: var(--rk-space-1); font-weight: 600; }
221
- [data-rk-part="field"] {
222
- display: block; width: 100%; height: var(--rk-control-size); padding: 0 var(--rk-space-2); font: inherit; color: var(--rk-fg);
223
- background: var(--rk-bg); border: var(--rk-border-width) solid var(--rk-border); border-radius: var(--rk-radius-s);
226
+ [data-rte-part="dialog"]::backdrop { background: rgb(0 0 0 / 0.45); }
227
+ [data-rte-part="dialog-title"] { margin: 0 0 var(--rte-space-3); font: 700 1.15em / 1.3 var(--rte-font-heading); }
228
+ [data-rte-part="field-label"] { display: block; margin-bottom: var(--rte-space-1); font-weight: 600; }
229
+ [data-rte-part="field"] {
230
+ display: block; width: 100%; height: var(--rte-control-size); padding: 0 var(--rte-space-2); font: inherit; color: var(--rte-fg);
231
+ background: var(--rte-bg); border: var(--rte-border-width) solid var(--rte-border); border-radius: var(--rte-radius-s);
224
232
  }
225
- [data-rk-part="field"][aria-invalid="true"] { border-color: var(--rk-danger); border-width: 2px; }
226
- [data-rk-part="field-check"] { display: flex; align-items: center; gap: var(--rk-space-2); margin-top: var(--rk-space-2); }
227
- [data-rk-part="field-check"] [data-rk-part="field"] { width: 1.1em; height: 1.1em; padding: 0; accent-color: var(--rk-accent); }
228
- [data-rk-part="field-check"] [data-rk-part="field-label"] { display: inline; margin: 0; font-weight: 400; }
229
- [data-rk-part="field-error"] { display: block; min-height: 1.4em; margin-top: var(--rk-space-1); color: var(--rk-danger); font-size: 0.9em; }
230
- [data-rk-part="actions"] { display: flex; justify-content: flex-end; gap: var(--rk-space-2); margin-top: var(--rk-space-3); }
231
- [data-rk-part="action"] {
232
- min-width: 5rem; height: var(--rk-control-size); padding: 0 var(--rk-space-3); font: inherit; font-weight: 600; color: var(--rk-fg);
233
- background: var(--rk-surface-raised); border: var(--rk-border-width) solid var(--rk-border); border-radius: var(--rk-radius-s); cursor: pointer;
233
+ [data-rte-part="field"][aria-invalid="true"] { border-color: var(--rte-danger); border-width: 2px; }
234
+ [data-rte-part="field-check"] { display: flex; align-items: center; gap: var(--rte-space-2); margin-top: var(--rte-space-2); }
235
+ [data-rte-part="field-check"] [data-rte-part="field"] { width: 1.1em; height: 1.1em; padding: 0; accent-color: var(--rte-accent); }
236
+ [data-rte-part="field-check"] [data-rte-part="field-label"] { display: inline; margin: 0; font-weight: 400; }
237
+ [data-rte-part="field-error"] { display: block; min-height: 1.4em; margin-top: var(--rte-space-1); color: var(--rte-danger); font-size: 0.9em; }
238
+ [data-rte-part="actions"] { display: flex; justify-content: flex-end; gap: var(--rte-space-2); margin-top: var(--rte-space-3); }
239
+ [data-rte-part="action"] {
240
+ min-width: 5rem; height: var(--rte-control-size); padding: 0 var(--rte-space-3); font: inherit; font-weight: 600; color: var(--rte-fg);
241
+ background: var(--rte-surface-raised); border: var(--rte-border-width) solid var(--rte-border); border-radius: var(--rte-radius-s); cursor: pointer;
234
242
  }
235
- [data-rk-part="action"][data-primary] { color: var(--rk-accent-fg); background: var(--rk-accent); border-color: var(--rk-accent); }
243
+ [data-rte-part="action"][data-primary] { color: var(--rte-accent-fg); background: var(--rte-accent); border-color: var(--rte-accent); }
236
244
 
237
245
  /* Matches painted by the page's highlight registry (nothing in the document changes). Dark text on a strong tint reads on any theme. */
238
- ::highlight(rk-find) { background-color: #ffe08a; color: #14181f; }
239
- ::highlight(rk-find-current) { background-color: #ff9632; color: #14181f; }
246
+ ::highlight(rte-find) { background-color: #ffe08a; color: #14181f; }
247
+ ::highlight(rte-find-current) { background-color: #ff9632; color: #14181f; }
240
248
 
241
- @media (prefers-reduced-motion: reduce) { .rk-root { --rk-motion-scale: 0; } }
249
+ @media (prefers-reduced-motion: reduce) { .rte-root { --rte-motion-scale: 0; } }
242
250
 
243
251
  /* Forced colors (Windows high contrast): use the user's colors, and real borders and outlines, which survive where shadows and tints do not. */
244
252
  @media (forced-colors: active) {
245
- .rk-root { border-color: CanvasText; }
246
- [data-rk-part="toolbar"] { border-bottom-color: CanvasText; }
247
- [data-rk-part="button"], [data-rk-part="select"], [data-rk-part="field"], [data-rk-part="action"] { border-color: ButtonText; color: ButtonText; background: ButtonFace; }
248
- [data-rk-part="button"][aria-pressed="true"] { border-color: Highlight; color: HighlightText; background: Highlight; border-bottom-width: 4px; }
249
- [data-rk-part="button"][aria-disabled="true"], [data-rk-part="select"][aria-disabled="true"] { color: GrayText; border-color: GrayText; opacity: 1; }
250
- [data-rk-part="content"] a { color: LinkText; }
251
- [data-rk-part="content"] [data-rk-cell-selected] { outline-color: Highlight; background: Canvas; }
252
- [data-rk-part="content"] img[data-rk-image-selected] { outline-color: Highlight; }
253
- .rk-root :focus-visible, .rk-root:focus-within { outline-color: Highlight; }
254
- [data-rk-part="separator"], [data-rk-part="menu-separator"] { background: CanvasText; }
255
- [data-rk-part="menu"] { border-color: CanvasText; background: Canvas; }
256
- ::highlight(rk-find) { background-color: Mark; color: MarkText; }
257
- ::highlight(rk-find-current) { background-color: Highlight; color: HighlightText; }
258
- [data-rk-part="find-field"] { border-color: ButtonText; }
259
- [data-rk-part="tooltip"] { color: CanvasText; background: Canvas; border: 1px solid CanvasText; }
260
- [data-rk-part="menu-item"] { color: ButtonText; }
261
- [data-rk-part="menu-item"]:hover:not([aria-disabled="true"]), [data-rk-part="menu-item"]:focus-visible { background: Highlight; color: HighlightText; }
262
- [data-rk-part="menu-item"][aria-disabled="true"] { color: GrayText; opacity: 1; }
253
+ .rte-root { border-color: CanvasText; }
254
+ [data-rte-part="toolbar"] { border-bottom-color: CanvasText; }
255
+ [data-rte-part="button"], [data-rte-part="select"], [data-rte-part="field"], [data-rte-part="action"] { border-color: ButtonText; color: ButtonText; background: ButtonFace; }
256
+ [data-rte-part="button"][aria-pressed="true"] { border-color: Highlight; color: HighlightText; background: Highlight; border-bottom-width: 4px; }
257
+ [data-rte-part="button"][aria-disabled="true"], [data-rte-part="select"][aria-disabled="true"] { color: GrayText; border-color: GrayText; opacity: 1; }
258
+ [data-rte-part="content"] a { color: LinkText; }
259
+ [data-rte-part="content"] [data-rte-cell-selected] { outline-color: Highlight; background: Canvas; }
260
+ [data-rte-part="content"] img[data-rte-image-selected] { outline-color: Highlight; }
261
+ .rte-root :focus-visible, .rte-root:focus-within { outline-color: Highlight; }
262
+ [data-rte-part="separator"], [data-rte-part="menu-separator"] { background: CanvasText; }
263
+ [data-rte-part="menu"], [data-rte-part="floating"] { border-color: CanvasText; background: Canvas; }
264
+ ::highlight(rte-find) { background-color: Mark; color: MarkText; }
265
+ ::highlight(rte-find-current) { background-color: Highlight; color: HighlightText; }
266
+ [data-rte-part="find-field"] { border-color: ButtonText; }
267
+ [data-rte-part="tooltip"] { color: CanvasText; background: Canvas; border: 1px solid CanvasText; }
268
+ [data-rte-part="menu-item"] { color: ButtonText; }
269
+ [data-rte-part="menu-item"]:hover:not([aria-disabled="true"]), [data-rte-part="menu-item"]:focus-visible { background: Highlight; color: HighlightText; }
270
+ [data-rte-part="menu-item"][aria-disabled="true"] { color: GrayText; opacity: 1; }
263
271
  }
264
272
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import { defineUi } from "./element.js";
2
+ defineUi();
@@ -0,0 +1,23 @@
1
+ import type { StyleProvider, Ui } from "./types.ts";
2
+ export declare class RteUiElement extends HTMLElement {
3
+ static observedAttributes: string[];
4
+ private current;
5
+ private extra;
6
+ connectedCallback(): void;
7
+ disconnectedCallback(): void;
8
+ attributeChangedCallback(name: string): void;
9
+ /** The interface, once it is built. */
10
+ get ui(): Ui | undefined;
11
+ /**
12
+ * Everything about the look that attributes cannot say: icons, messages, tokens, classes. The `theme`, `scheme` and `density`
13
+ * attributes win over the same names here.
14
+ */
15
+ get styleProvider(): StyleProvider;
16
+ set styleProvider(style: StyleProvider);
17
+ private readonly onReady;
18
+ private look;
19
+ private attach;
20
+ private detach;
21
+ }
22
+ /** Registers <rte-ui>. Safe to call more than once. */
23
+ export declare function defineUi(name?: string): void;
@@ -0,0 +1,62 @@
1
+ import { createStandardUi } from "./standard.js";
2
+ const STYLE_ATTRIBUTES = ["theme", "scheme", "density"];
3
+ export class RteUiElement extends HTMLElement {
4
+ static observedAttributes = [...STYLE_ATTRIBUTES, "selection-toolbar", "context-menu", "status"];
5
+ current;
6
+ extra = {};
7
+ connectedCallback() {
8
+ this.addEventListener("rte-ready", this.onReady);
9
+ this.attach();
10
+ }
11
+ disconnectedCallback() {
12
+ this.removeEventListener("rte-ready", this.onReady);
13
+ this.detach();
14
+ }
15
+ attributeChangedCallback(name) {
16
+ if (!this.current)
17
+ return;
18
+ if (STYLE_ATTRIBUTES.includes(name))
19
+ this.current.setStyle(this.look());
20
+ else {
21
+ this.detach();
22
+ this.attach();
23
+ }
24
+ }
25
+ get ui() { return this.current; }
26
+ get styleProvider() { return this.extra; }
27
+ set styleProvider(style) {
28
+ this.extra = style;
29
+ this.current?.setStyle(this.look());
30
+ }
31
+ onReady = () => { if (!this.current)
32
+ this.attach(); };
33
+ look() {
34
+ const given = {};
35
+ for (const name of STYLE_ATTRIBUTES) {
36
+ const value = this.getAttribute(name);
37
+ if (value)
38
+ given[name] = value;
39
+ }
40
+ return { ...this.extra, ...given };
41
+ }
42
+ attach() {
43
+ const host = this.querySelector("rte-editor");
44
+ const editor = host?.editor;
45
+ if (!host || !editor || this.current)
46
+ return;
47
+ this.current = createStandardUi(editor, {
48
+ root: host, style: this.look(),
49
+ selectionToolbar: this.hasAttribute("selection-toolbar"),
50
+ contextMenu: this.hasAttribute("context-menu"),
51
+ status: this.getAttribute("status") !== "false",
52
+ });
53
+ }
54
+ detach() {
55
+ this.current?.destroy();
56
+ this.current = undefined;
57
+ }
58
+ }
59
+ export function defineUi(name = "rte-ui") {
60
+ if (!customElements.get(name))
61
+ customElements.define(name, RteUiElement);
62
+ }