@rathkit/rte-ui 0.1.0-alpha.4 → 0.2.0-beta.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.
- package/README.md +1 -1
- package/css/base.css +195 -195
- package/dist/element-define.js +2 -2
- package/dist/element.d.ts +3 -3
- package/dist/element.js +6 -6
- package/dist/findbar.js +10 -10
- package/dist/floating.js +14 -14
- package/dist/shortcuts.js +2 -2
- package/dist/source.js +7 -7
- package/dist/standard.d.ts +0 -3
- package/dist/standard.js +0 -1
- package/dist/types.d.ts +2 -2
- package/dist/ui.js +31 -31
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @rathkit/rte-ui
|
|
2
2
|
|
|
3
|
-
> **
|
|
3
|
+
> **Beta.** Not for production yet: the APIs may still change between releases. Published to the `next` tag only.
|
|
4
4
|
|
|
5
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
|
|
package/css/base.css
CHANGED
|
@@ -1,272 +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-
|
|
3
|
-
Colors, sizes and fonts come from the --
|
|
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
|
-
.
|
|
9
|
-
--
|
|
10
|
-
--
|
|
11
|
-
--
|
|
12
|
-
--
|
|
13
|
-
--
|
|
14
|
-
--
|
|
15
|
-
--
|
|
16
|
-
--
|
|
17
|
-
--
|
|
18
|
-
--
|
|
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
|
-
.
|
|
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(--
|
|
30
|
-
color: var(--
|
|
31
|
-
background: var(--
|
|
32
|
-
border: var(--
|
|
33
|
-
border-radius: var(--
|
|
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
|
-
.
|
|
35
|
+
.rte-root *, .rte-root *::before, .rte-root *::after { box-sizing: inherit; }
|
|
36
36
|
|
|
37
37
|
/* ---- toolbar ---- */
|
|
38
|
-
[data-
|
|
38
|
+
[data-rte-part="toolbar"] {
|
|
39
39
|
display: flex;
|
|
40
40
|
flex-wrap: wrap;
|
|
41
41
|
align-items: center;
|
|
42
|
-
gap: var(--
|
|
43
|
-
padding: var(--
|
|
44
|
-
background: var(--
|
|
45
|
-
border-bottom: var(--
|
|
46
|
-
border-radius: calc(var(--
|
|
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-
|
|
48
|
+
[data-rte-part="separator"] {
|
|
49
49
|
align-self: stretch;
|
|
50
|
-
width: var(--
|
|
51
|
-
margin-inline: var(--
|
|
52
|
-
background: var(--
|
|
50
|
+
width: var(--rte-border-width);
|
|
51
|
+
margin-inline: var(--rte-space-1);
|
|
52
|
+
background: var(--rte-border-subtle);
|
|
53
53
|
}
|
|
54
|
-
[data-
|
|
55
|
-
[data-
|
|
56
|
-
min-width: var(--
|
|
57
|
-
height: var(--
|
|
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(--
|
|
59
|
+
padding: 0 var(--rte-space-2);
|
|
60
60
|
font: inherit;
|
|
61
|
-
color: var(--
|
|
61
|
+
color: var(--rte-fg);
|
|
62
62
|
background: transparent;
|
|
63
|
-
border: var(--
|
|
64
|
-
border-radius: var(--
|
|
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(--
|
|
66
|
+
transition: background-color calc(var(--rte-motion-duration) * var(--rte-motion-scale, 1)) ease;
|
|
67
67
|
}
|
|
68
|
-
[data-
|
|
69
|
-
[data-
|
|
70
|
-
[data-
|
|
71
|
-
[data-
|
|
72
|
-
[data-
|
|
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-
|
|
75
|
-
color: var(--
|
|
76
|
-
background: var(--
|
|
77
|
-
border-color: var(--
|
|
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-
|
|
81
|
-
[data-
|
|
82
|
-
[data-
|
|
83
|
-
[data-
|
|
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-
|
|
87
|
-
[data-
|
|
88
|
-
position: absolute; z-index: 10; inset-block-start: calc(100% + var(--
|
|
89
|
-
min-width: 14rem; padding: var(--
|
|
90
|
-
background: var(--
|
|
91
|
-
box-shadow: var(--
|
|
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-
|
|
94
|
-
[data-
|
|
95
|
-
display: flex; align-items: center; justify-content: space-between; gap: var(--
|
|
96
|
-
margin: 0; padding: 0 var(--
|
|
97
|
-
background: transparent; border: 0; border-radius: var(--
|
|
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-
|
|
100
|
-
[data-
|
|
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-
|
|
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-
|
|
105
|
-
[data-
|
|
106
|
-
[data-
|
|
107
|
-
[data-
|
|
108
|
-
[data-
|
|
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
109
|
|
|
110
110
|
/* ---- the selection toolbar: a short row of controls over selected text ---- */
|
|
111
|
-
[data-
|
|
112
|
-
position: absolute; z-index: 15; display: flex; align-items: center; gap: var(--
|
|
113
|
-
background: var(--
|
|
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
114
|
}
|
|
115
|
-
[data-
|
|
116
|
-
[data-
|
|
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); }
|
|
117
117
|
|
|
118
118
|
/* ---- tooltip: the control's name and key, shown near it on hover and keyboard focus ---- */
|
|
119
|
-
[data-
|
|
120
|
-
position: absolute; z-index: 20; max-width: 20rem; padding: var(--
|
|
121
|
-
color: var(--
|
|
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);
|
|
122
122
|
white-space: nowrap; pointer-events: auto;
|
|
123
123
|
}
|
|
124
|
-
[data-
|
|
124
|
+
[data-rte-part="tooltip"][hidden] { display: none; }
|
|
125
125
|
|
|
126
|
-
.
|
|
127
|
-
outline: var(--
|
|
128
|
-
outline-offset: var(--
|
|
126
|
+
.rte-root :focus-visible {
|
|
127
|
+
outline: var(--rte-focus-width) solid var(--rte-focus-color);
|
|
128
|
+
outline-offset: var(--rte-focus-offset);
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
/* ---- the writing area ---- */
|
|
132
|
-
[data-
|
|
133
|
-
padding: var(--
|
|
134
|
-
background: var(--
|
|
135
|
-
border-radius: 0 0 var(--
|
|
136
|
-
font-family: var(--
|
|
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);
|
|
137
137
|
}
|
|
138
|
-
[data-
|
|
139
|
-
[data-
|
|
140
|
-
/* The hint shown while the document is empty (the editor sets data-
|
|
141
|
-
[data-
|
|
142
|
-
[data-
|
|
143
|
-
[data-
|
|
144
|
-
[data-
|
|
145
|
-
[data-
|
|
146
|
-
[data-
|
|
147
|
-
[data-
|
|
148
|
-
[data-
|
|
149
|
-
[data-
|
|
150
|
-
[data-
|
|
151
|
-
[data-
|
|
152
|
-
[data-
|
|
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; }
|
|
153
153
|
/* A task's checkbox is a glyph in front of its text; clicking it ticks the task. */
|
|
154
|
-
[data-
|
|
155
|
-
[data-
|
|
156
|
-
[data-
|
|
157
|
-
[data-
|
|
158
|
-
[data-
|
|
159
|
-
[data-
|
|
160
|
-
[data-
|
|
161
|
-
[data-
|
|
162
|
-
[data-
|
|
163
|
-
[data-
|
|
164
|
-
[data-
|
|
165
|
-
[data-
|
|
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; }
|
|
166
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. */
|
|
167
|
-
[data-
|
|
168
|
-
[data-
|
|
169
|
-
[data-
|
|
170
|
-
[data-
|
|
171
|
-
[data-
|
|
172
|
-
[data-
|
|
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; }
|
|
173
173
|
/* Cells a selection runs across (the view marks them): a tint and an outline, so it is not color alone. */
|
|
174
|
-
[data-
|
|
175
|
-
[data-
|
|
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; }
|
|
176
176
|
/* Focus inside the writing area shows on the whole editor, not as a box around the text. */
|
|
177
|
-
.
|
|
178
|
-
.
|
|
179
|
-
.
|
|
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; }
|
|
180
180
|
|
|
181
181
|
/* ---- HTML source view: the text box takes the place of the writing area ---- */
|
|
182
|
-
[data-
|
|
183
|
-
[data-
|
|
184
|
-
display: block; width: 100%; min-height: 14em; margin: 0; padding: 0; font: 0.9em / 1.5 var(--
|
|
185
|
-
background: var(--
|
|
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;
|
|
186
186
|
}
|
|
187
|
-
[data-
|
|
187
|
+
[data-rte-part="source"][hidden] { display: none; }
|
|
188
188
|
|
|
189
189
|
/* ---- keyboard shortcuts list ---- */
|
|
190
|
-
[data-
|
|
191
|
-
[data-
|
|
192
|
-
[data-
|
|
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); }
|
|
193
193
|
|
|
194
194
|
/* ---- find and replace ---- */
|
|
195
|
-
[data-
|
|
196
|
-
[data-
|
|
197
|
-
[data-
|
|
198
|
-
[data-
|
|
199
|
-
[data-
|
|
200
|
-
[data-
|
|
201
|
-
[data-
|
|
202
|
-
flex: 1; min-width: 6em; height: var(--
|
|
203
|
-
background: var(--
|
|
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);
|
|
204
204
|
}
|
|
205
|
-
[data-
|
|
206
|
-
[data-
|
|
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; }
|
|
207
207
|
|
|
208
208
|
/* ---- status line ---- */
|
|
209
|
-
[data-
|
|
210
|
-
padding: var(--
|
|
211
|
-
border-top: var(--
|
|
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));
|
|
212
212
|
}
|
|
213
|
-
[data-
|
|
213
|
+
[data-rte-part="content"]:has(+ [data-rte-part="status"]) { border-radius: 0; }
|
|
214
214
|
|
|
215
215
|
/* ---- dialog ---- */
|
|
216
|
-
[data-
|
|
217
|
-
width: min(26rem, calc(100vw - 2 * var(--
|
|
218
|
-
padding: var(--
|
|
219
|
-
color: var(--
|
|
220
|
-
background: var(--
|
|
221
|
-
border: var(--
|
|
222
|
-
border-radius: var(--
|
|
223
|
-
box-shadow: var(--
|
|
224
|
-
font: var(--
|
|
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);
|
|
225
225
|
}
|
|
226
|
-
[data-
|
|
227
|
-
[data-
|
|
228
|
-
[data-
|
|
229
|
-
[data-
|
|
230
|
-
display: block; width: 100%; height: var(--
|
|
231
|
-
background: var(--
|
|
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);
|
|
232
232
|
}
|
|
233
|
-
[data-
|
|
234
|
-
[data-
|
|
235
|
-
[data-
|
|
236
|
-
[data-
|
|
237
|
-
[data-
|
|
238
|
-
[data-
|
|
239
|
-
[data-
|
|
240
|
-
min-width: 5rem; height: var(--
|
|
241
|
-
background: var(--
|
|
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;
|
|
242
242
|
}
|
|
243
|
-
[data-
|
|
243
|
+
[data-rte-part="action"][data-primary] { color: var(--rte-accent-fg); background: var(--rte-accent); border-color: var(--rte-accent); }
|
|
244
244
|
|
|
245
245
|
/* Matches painted by the page's highlight registry (nothing in the document changes). Dark text on a strong tint reads on any theme. */
|
|
246
|
-
::highlight(
|
|
247
|
-
::highlight(
|
|
246
|
+
::highlight(rte-find) { background-color: #ffe08a; color: #14181f; }
|
|
247
|
+
::highlight(rte-find-current) { background-color: #ff9632; color: #14181f; }
|
|
248
248
|
|
|
249
|
-
@media (prefers-reduced-motion: reduce) { .
|
|
249
|
+
@media (prefers-reduced-motion: reduce) { .rte-root { --rte-motion-scale: 0; } }
|
|
250
250
|
|
|
251
251
|
/* Forced colors (Windows high contrast): use the user's colors, and real borders and outlines, which survive where shadows and tints do not. */
|
|
252
252
|
@media (forced-colors: active) {
|
|
253
|
-
.
|
|
254
|
-
[data-
|
|
255
|
-
[data-
|
|
256
|
-
[data-
|
|
257
|
-
[data-
|
|
258
|
-
[data-
|
|
259
|
-
[data-
|
|
260
|
-
[data-
|
|
261
|
-
.
|
|
262
|
-
[data-
|
|
263
|
-
[data-
|
|
264
|
-
::highlight(
|
|
265
|
-
::highlight(
|
|
266
|
-
[data-
|
|
267
|
-
[data-
|
|
268
|
-
[data-
|
|
269
|
-
[data-
|
|
270
|
-
[data-
|
|
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; }
|
|
271
271
|
}
|
|
272
272
|
}
|
package/dist/element-define.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { defineUi } from "./element.js";
|
|
2
|
+
defineUi();
|
package/dist/element.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { StyleProvider, Ui } from "./types.ts";
|
|
2
|
-
export declare class
|
|
2
|
+
export declare class RteUiElement extends HTMLElement {
|
|
3
3
|
static observedAttributes: string[];
|
|
4
4
|
private current;
|
|
5
5
|
private extra;
|
|
@@ -19,5 +19,5 @@ export declare class RkUi extends HTMLElement {
|
|
|
19
19
|
private attach;
|
|
20
20
|
private detach;
|
|
21
21
|
}
|
|
22
|
-
/** Registers <
|
|
23
|
-
export declare function
|
|
22
|
+
/** Registers <rte-ui>. Safe to call more than once. */
|
|
23
|
+
export declare function defineUi(name?: string): void;
|
package/dist/element.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { createStandardUi } from "./standard.js";
|
|
2
2
|
const STYLE_ATTRIBUTES = ["theme", "scheme", "density"];
|
|
3
|
-
export class
|
|
3
|
+
export class RteUiElement extends HTMLElement {
|
|
4
4
|
static observedAttributes = [...STYLE_ATTRIBUTES, "selection-toolbar", "context-menu", "status"];
|
|
5
5
|
current;
|
|
6
6
|
extra = {};
|
|
7
7
|
connectedCallback() {
|
|
8
|
-
this.addEventListener("
|
|
8
|
+
this.addEventListener("rte-ready", this.onReady);
|
|
9
9
|
this.attach();
|
|
10
10
|
}
|
|
11
11
|
disconnectedCallback() {
|
|
12
|
-
this.removeEventListener("
|
|
12
|
+
this.removeEventListener("rte-ready", this.onReady);
|
|
13
13
|
this.detach();
|
|
14
14
|
}
|
|
15
15
|
attributeChangedCallback(name) {
|
|
@@ -40,7 +40,7 @@ export class RkUi extends HTMLElement {
|
|
|
40
40
|
return { ...this.extra, ...given };
|
|
41
41
|
}
|
|
42
42
|
attach() {
|
|
43
|
-
const host = this.querySelector("
|
|
43
|
+
const host = this.querySelector("rte-editor");
|
|
44
44
|
const editor = host?.editor;
|
|
45
45
|
if (!host || !editor || this.current)
|
|
46
46
|
return;
|
|
@@ -56,7 +56,7 @@ export class RkUi extends HTMLElement {
|
|
|
56
56
|
this.current = undefined;
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
-
export function
|
|
59
|
+
export function defineUi(name = "rte-ui") {
|
|
60
60
|
if (!customElements.get(name))
|
|
61
|
-
customElements.define(name,
|
|
61
|
+
customElements.define(name, RteUiElement);
|
|
62
62
|
}
|
package/dist/findbar.js
CHANGED
|
@@ -9,7 +9,7 @@ export function createFindBar(options) {
|
|
|
9
9
|
bar.setAttribute("role", "search");
|
|
10
10
|
bar.hidden = true;
|
|
11
11
|
const field = (label, name) => {
|
|
12
|
-
const id = `
|
|
12
|
+
const id = `rte-find-${++counter}`;
|
|
13
13
|
const wrap = part(document.createElement("div"), "find-row");
|
|
14
14
|
const text = part(document.createElement("label"), "find-label");
|
|
15
15
|
text.htmlFor = id;
|
|
@@ -33,7 +33,7 @@ export function createFindBar(options) {
|
|
|
33
33
|
else
|
|
34
34
|
b.textContent = label;
|
|
35
35
|
b.setAttribute("aria-label", label);
|
|
36
|
-
b.setAttribute("data-
|
|
36
|
+
b.setAttribute("data-rte-tip", label);
|
|
37
37
|
return b;
|
|
38
38
|
};
|
|
39
39
|
const textButton = (label) => {
|
|
@@ -66,7 +66,7 @@ export function createFindBar(options) {
|
|
|
66
66
|
replace.text.textContent = message("find.replaceWith");
|
|
67
67
|
for (const [el, key] of [[previous, "find.previous"], [next, "find.next"], [close, "find.close"], [matchCase, "find.matchCase"], [wholeWord, "find.wholeWord"]]) {
|
|
68
68
|
el.setAttribute("aria-label", message(key));
|
|
69
|
-
el.setAttribute("data-
|
|
69
|
+
el.setAttribute("data-rte-tip", message(key));
|
|
70
70
|
}
|
|
71
71
|
replaceOne.textContent = message("find.replace");
|
|
72
72
|
replaceAll.textContent = message("find.replaceAll");
|
|
@@ -82,8 +82,8 @@ export function createFindBar(options) {
|
|
|
82
82
|
if (!highlights || !Highlight || !editor.rangeFor)
|
|
83
83
|
return;
|
|
84
84
|
if (!state.open || state.matches.length === 0) {
|
|
85
|
-
highlights.delete("
|
|
86
|
-
highlights.delete("
|
|
85
|
+
highlights.delete("rte-find");
|
|
86
|
+
highlights.delete("rte-find-current");
|
|
87
87
|
return;
|
|
88
88
|
}
|
|
89
89
|
const ranges = [];
|
|
@@ -97,11 +97,11 @@ export function createFindBar(options) {
|
|
|
97
97
|
else
|
|
98
98
|
ranges.push(range);
|
|
99
99
|
});
|
|
100
|
-
highlights.set("
|
|
100
|
+
highlights.set("rte-find", new Highlight(...ranges));
|
|
101
101
|
if (current)
|
|
102
|
-
highlights.set("
|
|
102
|
+
highlights.set("rte-find-current", new Highlight(current));
|
|
103
103
|
else
|
|
104
|
-
highlights.delete("
|
|
104
|
+
highlights.delete("rte-find-current");
|
|
105
105
|
};
|
|
106
106
|
const render = () => {
|
|
107
107
|
const state = info();
|
|
@@ -179,8 +179,8 @@ export function createFindBar(options) {
|
|
|
179
179
|
if (announceTimer)
|
|
180
180
|
clearTimeout(announceTimer);
|
|
181
181
|
const highlights = registry();
|
|
182
|
-
highlights?.delete("
|
|
183
|
-
highlights?.delete("
|
|
182
|
+
highlights?.delete("rte-find");
|
|
183
|
+
highlights?.delete("rte-find-current");
|
|
184
184
|
bar.remove();
|
|
185
185
|
},
|
|
186
186
|
};
|
package/dist/floating.js
CHANGED
|
@@ -57,11 +57,11 @@ export function createFloating(options) {
|
|
|
57
57
|
group = next;
|
|
58
58
|
const button = part(document.createElement("button"), "button");
|
|
59
59
|
button.type = "button";
|
|
60
|
-
button.setAttribute("data-
|
|
60
|
+
button.setAttribute("data-rte-item", item.id);
|
|
61
61
|
const state = toolbar.state(item.id);
|
|
62
62
|
const label = message(`item.${item.id}`, state?.label ?? item.id);
|
|
63
63
|
button.setAttribute("aria-label", label);
|
|
64
|
-
button.setAttribute("data-
|
|
64
|
+
button.setAttribute("data-rte-tip", state?.shortcutLabel ? `${label} (${state.shortcutLabel})` : label);
|
|
65
65
|
if (!iconInto(button, state?.icon)) {
|
|
66
66
|
const words = part(document.createElement("span"), "label");
|
|
67
67
|
words.textContent = label;
|
|
@@ -77,7 +77,7 @@ export function createFloating(options) {
|
|
|
77
77
|
let unbind;
|
|
78
78
|
draw();
|
|
79
79
|
const stopItems = toolbar.subscribe(() => {
|
|
80
|
-
const drawn = [...bar.querySelectorAll("[data-
|
|
80
|
+
const drawn = [...bar.querySelectorAll("[data-rte-item]")].map((el) => el.getAttribute("data-rte-item")).join("\u0000");
|
|
81
81
|
if (drawn !== toolbar.items.filter((i) => i.kind === "button" || i.kind === "toggle" || i.kind === "dialog").map((i) => i.id).join("\u0000"))
|
|
82
82
|
draw();
|
|
83
83
|
});
|
|
@@ -148,7 +148,7 @@ export function createFloating(options) {
|
|
|
148
148
|
touched = false;
|
|
149
149
|
if (e.key === "F10" && e.altKey && !bar.hidden) {
|
|
150
150
|
e.preventDefault();
|
|
151
|
-
bar.querySelector("[data-
|
|
151
|
+
bar.querySelector("[data-rte-item][tabindex='0']")?.focus();
|
|
152
152
|
}
|
|
153
153
|
});
|
|
154
154
|
on(bar, "keydown", (e) => { if (e.key === "Escape") {
|
|
@@ -165,12 +165,12 @@ export function createFloating(options) {
|
|
|
165
165
|
seed(CONTEXT_SLOT, CONTEXT_DEFAULTS);
|
|
166
166
|
const toolbar = createToolbar(editor, { slot: CONTEXT_SLOT });
|
|
167
167
|
const popup = part(document.createElement("div"), "menu");
|
|
168
|
-
popup.setAttribute("data-
|
|
168
|
+
popup.setAttribute("data-rte-context", "");
|
|
169
169
|
const anchor = document.createElement("button");
|
|
170
170
|
anchor.type = "button";
|
|
171
171
|
anchor.hidden = true;
|
|
172
172
|
anchor.setAttribute("aria-label", message("context.label"));
|
|
173
|
-
anchor.id = `
|
|
173
|
+
anchor.id = `rte-context-anchor-${++counter}`;
|
|
174
174
|
anchor.focus = () => editor.focus();
|
|
175
175
|
root.append(anchor, popup);
|
|
176
176
|
const entries = new Map();
|
|
@@ -194,8 +194,8 @@ export function createFloating(options) {
|
|
|
194
194
|
entries.set(item.id, item);
|
|
195
195
|
const entry = part(document.createElement("button"), "menu-item");
|
|
196
196
|
entry.type = "button";
|
|
197
|
-
entry.setAttribute("data-
|
|
198
|
-
entry.setAttribute("data-
|
|
197
|
+
entry.setAttribute("data-rte-menuitem", "");
|
|
198
|
+
entry.setAttribute("data-rte-menu-item", item.id);
|
|
199
199
|
if (item.kind === "toggle")
|
|
200
200
|
entry.setAttribute("role", "menuitemcheckbox");
|
|
201
201
|
entry.append(part(document.createElement("span"), "label"), part(document.createElement("span"), "menu-keys"));
|
|
@@ -214,7 +214,7 @@ export function createFloating(options) {
|
|
|
214
214
|
popup.style.top = `${Math.max(4, Math.min(at.y, view.clientHeight - h - 4))}px`;
|
|
215
215
|
},
|
|
216
216
|
onSelect: (entry) => {
|
|
217
|
-
const id = entry.getAttribute("data-
|
|
217
|
+
const id = entry.getAttribute("data-rte-menu-item") ?? "";
|
|
218
218
|
const item = entries.get(id);
|
|
219
219
|
if (item?.kind === "dialog") {
|
|
220
220
|
options.openDialog(item, returnTo);
|
|
@@ -228,17 +228,17 @@ export function createFloating(options) {
|
|
|
228
228
|
};
|
|
229
229
|
const words = () => {
|
|
230
230
|
anchor.setAttribute("aria-label", message("context.label"));
|
|
231
|
-
for (const entry of popup.querySelectorAll("[data-
|
|
232
|
-
const id = entry.getAttribute("data-
|
|
231
|
+
for (const entry of popup.querySelectorAll("[data-rte-menu-item]")) {
|
|
232
|
+
const id = entry.getAttribute("data-rte-menu-item") ?? "";
|
|
233
233
|
const item = entries.get(id);
|
|
234
234
|
const state = toolbar.state(id);
|
|
235
235
|
if (!item || !state)
|
|
236
236
|
continue;
|
|
237
237
|
const label = message(`item.${id}`, state.label);
|
|
238
|
-
const name = entry.querySelector('[data-
|
|
238
|
+
const name = entry.querySelector('[data-rte-part="label"]');
|
|
239
239
|
if (name)
|
|
240
240
|
name.textContent = label;
|
|
241
|
-
const keys = entry.querySelector('[data-
|
|
241
|
+
const keys = entry.querySelector('[data-rte-part="menu-keys"]');
|
|
242
242
|
if (keys)
|
|
243
243
|
keys.textContent = state.shortcutLabel ?? "";
|
|
244
244
|
entry.setAttribute("aria-disabled", String(state.disabled));
|
|
@@ -248,7 +248,7 @@ export function createFloating(options) {
|
|
|
248
248
|
};
|
|
249
249
|
draw();
|
|
250
250
|
const stopState = toolbar.subscribe(() => {
|
|
251
|
-
const drawn = [...popup.querySelectorAll("[data-
|
|
251
|
+
const drawn = [...popup.querySelectorAll("[data-rte-menu-item]")].map((el) => el.getAttribute("data-rte-menu-item")).join("\u0000");
|
|
252
252
|
if (drawn !== toolbar.items.filter((i) => i.kind === "button" || i.kind === "toggle" || i.kind === "dialog").map((i) => i.id).join("\u0000"))
|
|
253
253
|
draw();
|
|
254
254
|
else
|
package/dist/shortcuts.js
CHANGED
|
@@ -3,9 +3,9 @@ let counter = 0;
|
|
|
3
3
|
export function createShortcuts(options) {
|
|
4
4
|
const { editor, root, message } = options;
|
|
5
5
|
const element = options.part(document.createElement("dialog"), "dialog");
|
|
6
|
-
const titleId = `
|
|
6
|
+
const titleId = `rte-shortcuts-title-${++counter}`;
|
|
7
7
|
element.setAttribute("aria-labelledby", titleId);
|
|
8
|
-
element.setAttribute("data-
|
|
8
|
+
element.setAttribute("data-rte-shortcuts", "");
|
|
9
9
|
const title = options.part(document.createElement("h2"), "dialog-title");
|
|
10
10
|
title.id = titleId;
|
|
11
11
|
const table = options.part(document.createElement("table"), "shortcuts");
|
package/dist/source.js
CHANGED
|
@@ -8,25 +8,25 @@ export function createSourceView(options) {
|
|
|
8
8
|
content.appendChild(area);
|
|
9
9
|
let original = "";
|
|
10
10
|
let opener;
|
|
11
|
-
const button = () => toolbar.querySelector('[data-
|
|
11
|
+
const button = () => toolbar.querySelector('[data-rte-item="source"]');
|
|
12
12
|
const close = (apply) => {
|
|
13
13
|
if (area.hidden)
|
|
14
14
|
return;
|
|
15
15
|
const changed = area.value !== original;
|
|
16
16
|
area.hidden = true;
|
|
17
|
-
content.removeAttribute("data-
|
|
17
|
+
content.removeAttribute("data-rte-source");
|
|
18
18
|
button()?.setAttribute("aria-pressed", "false");
|
|
19
19
|
if (apply && changed)
|
|
20
20
|
editor.commands.run("document.setHtml", { html: area.value });
|
|
21
21
|
editor.focus();
|
|
22
|
-
(opener ?? button())?.removeAttribute("data-
|
|
22
|
+
(opener ?? button())?.removeAttribute("data-rte-busy");
|
|
23
23
|
};
|
|
24
24
|
const open = (from) => {
|
|
25
25
|
opener = from;
|
|
26
26
|
original = editor.getHtml?.("\n") ?? "";
|
|
27
27
|
area.value = original;
|
|
28
28
|
area.hidden = false;
|
|
29
|
-
content.setAttribute("data-
|
|
29
|
+
content.setAttribute("data-rte-source", "");
|
|
30
30
|
button()?.setAttribute("aria-pressed", "true");
|
|
31
31
|
area.focus();
|
|
32
32
|
area.setSelectionRange(0, 0);
|
|
@@ -34,8 +34,8 @@ export function createSourceView(options) {
|
|
|
34
34
|
const guard = (e) => {
|
|
35
35
|
if (area.hidden)
|
|
36
36
|
return;
|
|
37
|
-
const control = e.target?.closest("[data-
|
|
38
|
-
if (!control || control.getAttribute("data-
|
|
37
|
+
const control = e.target?.closest("[data-rte-item]");
|
|
38
|
+
if (!control || control.getAttribute("data-rte-item") === "source")
|
|
39
39
|
return;
|
|
40
40
|
if (e.type === "keydown" && !["Enter", " "].includes(e.key))
|
|
41
41
|
return;
|
|
@@ -61,7 +61,7 @@ export function createSourceView(options) {
|
|
|
61
61
|
toolbar.removeEventListener("click", guard, true);
|
|
62
62
|
toolbar.removeEventListener("keydown", guard, true);
|
|
63
63
|
area.remove();
|
|
64
|
-
content.removeAttribute("data-
|
|
64
|
+
content.removeAttribute("data-rte-source");
|
|
65
65
|
},
|
|
66
66
|
};
|
|
67
67
|
}
|
package/dist/standard.d.ts
CHANGED
package/dist/standard.js
CHANGED
package/dist/types.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Toolbar, ItemState, HeadlessEditor } from "@rathkit/rte-headless";
|
|
2
2
|
import type { Counts, UiItem } from "@rathkit/rte-core";
|
|
3
3
|
import type { TokenName } from "@rathkit/rte-themes";
|
|
4
|
-
/** The parts the interface is made of. Selectors and `classes` address them by these names (as `data-
|
|
4
|
+
/** The parts the interface is made of. Selectors and `classes` address them by these names (as `data-rte-part`). */
|
|
5
5
|
export type PartName = "root" | "toolbar" | "button" | "select" | "separator" | "icon" | "label" | "content" | "menu-host" | "menu" | "menu-item" | "menu-separator" | "menu-keys" | "swatch" | "tooltip" | "status" | "source" | "shortcuts" | "floating" | "findbar" | "find-line" | "find-row" | "find-label" | "find-field" | "find-count" | "dialog" | "dialog-title" | "field-label" | "field-check" | "field" | "field-error" | "actions" | "action";
|
|
6
6
|
/** Words the interface says. Each has a default in English; this table is also the way to translate it. */
|
|
7
7
|
export type MessageKey = "toolbar.label" | "floating.label" | "context.label" | "dialog.ok" | "dialog.cancel" | "field.required" | "field.url" | "toggle.on" | "toggle.off" | "status.word" | "status.words" | "status.characters" | "status.selection" | "source.label" | "shortcuts.title" | "shortcuts.action" | "shortcuts.keys" | "shortcuts.close" | "find.find" | "find.replaceWith" | "find.previous" | "find.next" | "find.close" | "find.matchCase" | "find.wholeWord" | "find.replace" | "find.replaceAll" | "find.none" | "find.count" | "find.total"
|
|
@@ -52,7 +52,7 @@ export type UiEditor = HeadlessEditor & {
|
|
|
52
52
|
}): Range | null;
|
|
53
53
|
};
|
|
54
54
|
export interface UiOptions {
|
|
55
|
-
/** The element the interface is built in. It gets the `
|
|
55
|
+
/** The element the interface is built in. It gets the `rte-root` class. */
|
|
56
56
|
readonly root: HTMLElement;
|
|
57
57
|
readonly style?: StyleProvider;
|
|
58
58
|
/** A line under the writing area with the number of words and characters. Default: on, when the editor can count. */
|
package/dist/ui.js
CHANGED
|
@@ -19,7 +19,7 @@ export function createUi(editor, options) {
|
|
|
19
19
|
const appliedClasses = new Map();
|
|
20
20
|
const parts = new Map();
|
|
21
21
|
const part = (el, name) => {
|
|
22
|
-
el.setAttribute("data-
|
|
22
|
+
el.setAttribute("data-rte-part", name);
|
|
23
23
|
let set = parts.get(name);
|
|
24
24
|
if (!set)
|
|
25
25
|
parts.set(name, (set = new Set()));
|
|
@@ -29,7 +29,7 @@ export function createUi(editor, options) {
|
|
|
29
29
|
return el;
|
|
30
30
|
};
|
|
31
31
|
const message = (key, fallback = "", values = {}) => text(style.messages, key, fallback, values);
|
|
32
|
-
root.classList.add("
|
|
32
|
+
root.classList.add("rte-root");
|
|
33
33
|
part(root, "root");
|
|
34
34
|
const toolbarEl = part(document.createElement("div"), "toolbar");
|
|
35
35
|
const contentEl = part(document.createElement("div"), "content");
|
|
@@ -82,9 +82,9 @@ export function createUi(editor, options) {
|
|
|
82
82
|
: given;
|
|
83
83
|
style = next;
|
|
84
84
|
const set = (name, value) => (value === undefined ? root.removeAttribute(name) : root.setAttribute(name, value));
|
|
85
|
-
set("data-
|
|
86
|
-
set("data-
|
|
87
|
-
set("data-
|
|
85
|
+
set("data-rte-theme", next.theme);
|
|
86
|
+
set("data-rte-scheme", next.scheme);
|
|
87
|
+
set("data-rte-density", next.density);
|
|
88
88
|
for (const name of [...appliedTokens])
|
|
89
89
|
if (!(next.tokens && name in next.tokens)) {
|
|
90
90
|
root.style.removeProperty(name);
|
|
@@ -130,7 +130,7 @@ export function createUi(editor, options) {
|
|
|
130
130
|
return undefined;
|
|
131
131
|
if (item.kind === "select") {
|
|
132
132
|
const select = part(document.createElement("select"), "select");
|
|
133
|
-
select.setAttribute("data-
|
|
133
|
+
select.setAttribute("data-rte-item", item.id);
|
|
134
134
|
for (const o of item.options) {
|
|
135
135
|
const option = document.createElement("option");
|
|
136
136
|
option.value = String(o.value);
|
|
@@ -144,14 +144,14 @@ export function createUi(editor, options) {
|
|
|
144
144
|
const customize = style.parts?.button;
|
|
145
145
|
if (customize) {
|
|
146
146
|
const el = customize({ item, state, run: (value) => toolbar.activate(item.id, value) });
|
|
147
|
-
el.setAttribute("data-
|
|
148
|
-
el.setAttribute("data-
|
|
147
|
+
el.setAttribute("data-rte-part", "button");
|
|
148
|
+
el.setAttribute("data-rte-item", item.id);
|
|
149
149
|
(parts.get("button") ?? parts.set("button", new Set()).get("button"))?.add(el);
|
|
150
150
|
return el;
|
|
151
151
|
}
|
|
152
152
|
const button = part(document.createElement("button"), "button");
|
|
153
153
|
button.type = "button";
|
|
154
|
-
button.setAttribute("data-
|
|
154
|
+
button.setAttribute("data-rte-item", item.id);
|
|
155
155
|
const icon = part(document.createElement("span"), "icon");
|
|
156
156
|
icon.setAttribute("aria-hidden", "true");
|
|
157
157
|
if (setIcon(icon, iconFor(state.icon))) {
|
|
@@ -171,7 +171,7 @@ export function createUi(editor, options) {
|
|
|
171
171
|
const host = part(document.createElement("span"), "menu-host");
|
|
172
172
|
const trigger = part(document.createElement("button"), "button");
|
|
173
173
|
trigger.type = "button";
|
|
174
|
-
trigger.setAttribute("data-
|
|
174
|
+
trigger.setAttribute("data-rte-item", item.id);
|
|
175
175
|
const icon = part(document.createElement("span"), "icon");
|
|
176
176
|
icon.setAttribute("aria-hidden", "true");
|
|
177
177
|
if (setIcon(icon, iconFor(state.icon))) {
|
|
@@ -200,8 +200,8 @@ export function createUi(editor, options) {
|
|
|
200
200
|
menuItems.set(child.id, child);
|
|
201
201
|
const entry = part(document.createElement("button"), "menu-item");
|
|
202
202
|
entry.type = "button";
|
|
203
|
-
entry.setAttribute("data-
|
|
204
|
-
entry.setAttribute("data-
|
|
203
|
+
entry.setAttribute("data-rte-menuitem", "");
|
|
204
|
+
entry.setAttribute("data-rte-menu-item", child.id);
|
|
205
205
|
const words = part(document.createElement("span"), "label");
|
|
206
206
|
const keys = part(document.createElement("span"), "menu-keys");
|
|
207
207
|
if (child.kind !== "dialog" && child.swatch) {
|
|
@@ -225,7 +225,7 @@ export function createUi(editor, options) {
|
|
|
225
225
|
popup.style.translate = `${Math.round(dx)}px`;
|
|
226
226
|
},
|
|
227
227
|
onSelect: (entry) => {
|
|
228
|
-
const id = entry.getAttribute("data-
|
|
228
|
+
const id = entry.getAttribute("data-rte-menu-item") ?? "";
|
|
229
229
|
const child = menuItems.get(id);
|
|
230
230
|
if (child?.kind === "dialog") {
|
|
231
231
|
openDialog(child, trigger);
|
|
@@ -238,25 +238,25 @@ export function createUi(editor, options) {
|
|
|
238
238
|
editor.focus();
|
|
239
239
|
},
|
|
240
240
|
});
|
|
241
|
-
for (const entry of popup.querySelectorAll("[data-
|
|
242
|
-
if (menuItems.get(entry.getAttribute("data-
|
|
241
|
+
for (const entry of popup.querySelectorAll("[data-rte-menu-item]")) {
|
|
242
|
+
if (menuItems.get(entry.getAttribute("data-rte-menu-item") ?? "")?.kind === "toggle")
|
|
243
243
|
entry.setAttribute("role", "menuitemcheckbox");
|
|
244
244
|
}
|
|
245
245
|
menus.push(menu);
|
|
246
246
|
return host;
|
|
247
247
|
}
|
|
248
248
|
function menuEntries() {
|
|
249
|
-
for (const entry of toolbarEl.querySelectorAll("[data-
|
|
250
|
-
const id = entry.getAttribute("data-
|
|
249
|
+
for (const entry of toolbarEl.querySelectorAll("[data-rte-menu-item]")) {
|
|
250
|
+
const id = entry.getAttribute("data-rte-menu-item") ?? "";
|
|
251
251
|
const child = menuItems.get(id);
|
|
252
252
|
const state = toolbar.state(id);
|
|
253
253
|
if (!child || !state)
|
|
254
254
|
continue;
|
|
255
255
|
const label = labelOf(child);
|
|
256
|
-
const words = entry.querySelector('[data-
|
|
256
|
+
const words = entry.querySelector('[data-rte-part="label"]');
|
|
257
257
|
if (words)
|
|
258
258
|
words.textContent = label;
|
|
259
|
-
const keys = entry.querySelector('[data-
|
|
259
|
+
const keys = entry.querySelector('[data-rte-part="menu-keys"]');
|
|
260
260
|
if (keys)
|
|
261
261
|
keys.textContent = state.shortcutLabel ?? "";
|
|
262
262
|
if (state.shortcut)
|
|
@@ -305,17 +305,17 @@ export function createUi(editor, options) {
|
|
|
305
305
|
menuEntries();
|
|
306
306
|
}
|
|
307
307
|
function names() {
|
|
308
|
-
for (const el of toolbarEl.querySelectorAll("[data-
|
|
309
|
-
const id = el.getAttribute("data-
|
|
308
|
+
for (const el of toolbarEl.querySelectorAll("[data-rte-item]")) {
|
|
309
|
+
const id = el.getAttribute("data-rte-item") ?? "";
|
|
310
310
|
const item = toolbar.items.find((i) => i.id === id);
|
|
311
311
|
const state = toolbar.state(id);
|
|
312
312
|
if (!item || !state)
|
|
313
313
|
continue;
|
|
314
314
|
const label = labelOf(item);
|
|
315
315
|
el.setAttribute("aria-label", label);
|
|
316
|
-
el.setAttribute("data-
|
|
316
|
+
el.setAttribute("data-rte-tip", state.shortcutLabel ? `${label} (${state.shortcutLabel})` : label);
|
|
317
317
|
if (el.tagName === "BUTTON") {
|
|
318
|
-
const word = el.querySelector('[data-
|
|
318
|
+
const word = el.querySelector('[data-rte-part="label"]');
|
|
319
319
|
if (word)
|
|
320
320
|
word.textContent = label;
|
|
321
321
|
}
|
|
@@ -332,10 +332,10 @@ export function createUi(editor, options) {
|
|
|
332
332
|
});
|
|
333
333
|
let drawn = "";
|
|
334
334
|
const onToolbarClick = (e) => {
|
|
335
|
-
const el = e.target?.closest("[data-
|
|
335
|
+
const el = e.target?.closest("[data-rte-item]");
|
|
336
336
|
if (!el)
|
|
337
337
|
return;
|
|
338
|
-
const item = toolbar.items.find((i) => i.id === el.getAttribute("data-
|
|
338
|
+
const item = toolbar.items.find((i) => i.id === el.getAttribute("data-rte-item"));
|
|
339
339
|
const state = item ? toolbar.state(item.id) : undefined;
|
|
340
340
|
if (item?.kind === "toggle" && state && !state.disabled) {
|
|
341
341
|
const group = item.group === "align" || item.group === "lists";
|
|
@@ -356,7 +356,7 @@ export function createUi(editor, options) {
|
|
|
356
356
|
let entry = dialogs.get(item.id);
|
|
357
357
|
if (!entry) {
|
|
358
358
|
const element = part(document.createElement("dialog"), "dialog");
|
|
359
|
-
const titleId = `
|
|
359
|
+
const titleId = `rte-dialog-title-${++counter}`;
|
|
360
360
|
element.setAttribute("aria-labelledby", titleId);
|
|
361
361
|
const form = document.createElement("form");
|
|
362
362
|
form.noValidate = true;
|
|
@@ -364,7 +364,7 @@ export function createUi(editor, options) {
|
|
|
364
364
|
title.id = titleId;
|
|
365
365
|
form.appendChild(title);
|
|
366
366
|
for (const field of item.fields) {
|
|
367
|
-
const inputId = `
|
|
367
|
+
const inputId = `rte-field-${++counter}`;
|
|
368
368
|
const label = part(document.createElement("label"), "field-label");
|
|
369
369
|
label.htmlFor = inputId;
|
|
370
370
|
label.textContent = field.label;
|
|
@@ -388,7 +388,7 @@ export function createUi(editor, options) {
|
|
|
388
388
|
if (field.required)
|
|
389
389
|
input.required = true;
|
|
390
390
|
const error = part(document.createElement("span"), "field-error");
|
|
391
|
-
error.setAttribute("data-
|
|
391
|
+
error.setAttribute("data-rte-error", field.name);
|
|
392
392
|
if (field.type === "checkbox") {
|
|
393
393
|
const row = part(document.createElement("div"), "field-check");
|
|
394
394
|
row.append(input, label);
|
|
@@ -436,7 +436,7 @@ export function createUi(editor, options) {
|
|
|
436
436
|
for (const f of item.fields) {
|
|
437
437
|
const bad = entry.form.querySelector(`[name="${f.name}"]`);
|
|
438
438
|
bad?.removeAttribute("aria-invalid");
|
|
439
|
-
const err = entry.form.querySelector(`[data-
|
|
439
|
+
const err = entry.form.querySelector(`[data-rte-error="${f.name}"]`);
|
|
440
440
|
if (err)
|
|
441
441
|
err.textContent = "";
|
|
442
442
|
}
|
|
@@ -484,8 +484,8 @@ export function createUi(editor, options) {
|
|
|
484
484
|
toolbar.destroy();
|
|
485
485
|
toolbarEl.remove();
|
|
486
486
|
contentEl.replaceWith(...contentEl.childNodes);
|
|
487
|
-
root.classList.remove("
|
|
488
|
-
for (const name of ["data-
|
|
487
|
+
root.classList.remove("rte-root");
|
|
488
|
+
for (const name of ["data-rte-theme", "data-rte-scheme", "data-rte-density", "data-rte-part"])
|
|
489
489
|
root.removeAttribute(name);
|
|
490
490
|
for (const t of appliedTokens)
|
|
491
491
|
root.style.removeProperty(t);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rathkit/rte-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0-beta.0",
|
|
4
4
|
"description": "The styled editor interface: a toolbar and dialogs on the headless layer, themeable with tokens, presets, icons and messages.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"rich-text",
|
|
@@ -61,10 +61,10 @@
|
|
|
61
61
|
"./base.css": "./css/base.css"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
|
-
"@rathkit/rte-core": ">=0.
|
|
65
|
-
"@rathkit/rte-headless": ">=0.
|
|
66
|
-
"@rathkit/rte-themes": ">=0.
|
|
67
|
-
"@rathkit/rte-icons": ">=0.
|
|
64
|
+
"@rathkit/rte-core": ">=0.2.0-beta.0 <0.3.0",
|
|
65
|
+
"@rathkit/rte-headless": ">=0.2.0-beta.0 <0.3.0",
|
|
66
|
+
"@rathkit/rte-themes": ">=0.2.0-beta.0 <0.3.0",
|
|
67
|
+
"@rathkit/rte-icons": ">=0.2.0-beta.0 <0.3.0"
|
|
68
68
|
},
|
|
69
69
|
"peerDependenciesMeta": {
|
|
70
70
|
"@rathkit/rte-core": {
|