@openleaf-editor/ui 0.1.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/LICENSE +202 -0
- package/dist/dialog.d.ts +25 -0
- package/dist/dialog.d.ts.map +1 -0
- package/dist/dialog.js +266 -0
- package/dist/dialog.js.map +1 -0
- package/dist/icons.d.ts +53 -0
- package/dist/icons.d.ts.map +1 -0
- package/dist/icons.js +143 -0
- package/dist/icons.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/items.d.ts +13 -0
- package/dist/items.d.ts.map +1 -0
- package/dist/items.js +190 -0
- package/dist/items.js.map +1 -0
- package/dist/openleaf.css +426 -0
- package/dist/registry.d.ts +85 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +77 -0
- package/dist/registry.js.map +1 -0
- package/dist/skins.d.ts +114 -0
- package/dist/skins.d.ts.map +1 -0
- package/dist/skins.js +226 -0
- package/dist/skins.js.map +1 -0
- package/dist/styles.d.ts +65 -0
- package/dist/styles.d.ts.map +1 -0
- package/dist/styles.js +532 -0
- package/dist/styles.js.map +1 -0
- package/dist/toolbar.d.ts +76 -0
- package/dist/toolbar.d.ts.map +1 -0
- package/dist/toolbar.js +509 -0
- package/dist/toolbar.js.map +1 -0
- package/package.json +39 -0
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
.ol-editor {
|
|
2
|
+
/* Two families, named before either is public API. A singular
|
|
3
|
+
`--openleaf-font` implies "the" font, and source view already has a
|
|
4
|
+
monospace surface -- bolting a mono token on beside a singular name later
|
|
5
|
+
is the awkward outcome worth avoiding now. `--openleaf-font` is still
|
|
6
|
+
honoured as a fallback. */
|
|
7
|
+
--ol-font: var(--openleaf-font-ui, var(--openleaf-font, system-ui, -apple-system, "Segoe UI", sans-serif));
|
|
8
|
+
--ol-font-mono: var(--openleaf-font-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
|
|
9
|
+
--ol-font-size: var(--openleaf-font-size, 14px);
|
|
10
|
+
--ol-radius: var(--openleaf-radius, 4px);
|
|
11
|
+
--ol-text: var(--openleaf-color-text, #1f2328);
|
|
12
|
+
--ol-text-muted: var(--openleaf-color-text-muted, #59636e);
|
|
13
|
+
--ol-surface: var(--openleaf-color-surface, #ffffff);
|
|
14
|
+
--ol-surface-hover: var(--openleaf-color-surface-hover, #f0f1f3);
|
|
15
|
+
--ol-surface-active: var(--openleaf-color-surface-active, #dbe9ff);
|
|
16
|
+
--ol-border: var(--openleaf-color-border, #d1d9e0);
|
|
17
|
+
--ol-accent: var(--openleaf-color-accent, #0550ae);
|
|
18
|
+
--ol-focus: var(--openleaf-color-focus, #0969da);
|
|
19
|
+
--ol-button-size: var(--openleaf-button-size, 32px);
|
|
20
|
+
--ol-icon-size: var(--openleaf-icon-size, 16px);
|
|
21
|
+
--ol-gap: var(--openleaf-gap, 2px);
|
|
22
|
+
/* A 2009 WordPress admin bar sits at z-index 99999 and Drupal's toolbar plays
|
|
23
|
+
similar games. Without a sanctioned escape hatch integrators fix a toolbar
|
|
24
|
+
rendered behind a sticky host header with !important -- the exact thing the
|
|
25
|
+
token API exists to prevent. */
|
|
26
|
+
--ol-z: var(--openleaf-z-index, 1);
|
|
27
|
+
/* Thickness and offset, not just colour: "you can change the colour but not
|
|
28
|
+
the thickness" is a poor answer to a Section 508 team citing WCAG 2.2
|
|
29
|
+
Focus Appearance. */
|
|
30
|
+
--ol-focus-width: var(--openleaf-focus-width, 2px);
|
|
31
|
+
--ol-focus-offset: var(--openleaf-focus-offset, 1px);
|
|
32
|
+
|
|
33
|
+
display: block;
|
|
34
|
+
color: var(--ol-text);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* Three ways into the dark palette, and one way out of it.
|
|
38
|
+
`data-ol-scheme` is set from the applied skin's declared scheme, and a skin
|
|
39
|
+
that declares one outranks both the attribute and the system -- it has to,
|
|
40
|
+
because its tokens have already replaced the palette outright. Without the
|
|
41
|
+
`:not()` guards a light skin on a dark machine would take these fallbacks for
|
|
42
|
+
every token it did not itself set, which is a light surface carrying dark-mode
|
|
43
|
+
muted text. */
|
|
44
|
+
@media (prefers-color-scheme: dark) {
|
|
45
|
+
.ol-editor:not([data-ol-theme="light"]):not([data-ol-scheme]) {
|
|
46
|
+
--ol-text: var(--openleaf-color-text, #e6edf3);
|
|
47
|
+
--ol-text-muted: var(--openleaf-color-text-muted, #9198a1);
|
|
48
|
+
--ol-surface: var(--openleaf-color-surface, #0d1117);
|
|
49
|
+
--ol-surface-hover: var(--openleaf-color-surface-hover, #21262d);
|
|
50
|
+
--ol-surface-active: var(--openleaf-color-surface-active, #1f3a5f);
|
|
51
|
+
--ol-border: var(--openleaf-color-border, #3d444d);
|
|
52
|
+
--ol-accent: var(--openleaf-color-accent, #79c0ff);
|
|
53
|
+
--ol-focus: var(--openleaf-color-focus, #79c0ff);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.ol-editor[data-ol-theme="dark"]:not([data-ol-scheme]) {
|
|
58
|
+
--ol-text: var(--openleaf-color-text, #e6edf3);
|
|
59
|
+
--ol-text-muted: var(--openleaf-color-text-muted, #9198a1);
|
|
60
|
+
--ol-surface: var(--openleaf-color-surface, #0d1117);
|
|
61
|
+
--ol-surface-hover: var(--openleaf-color-surface-hover, #21262d);
|
|
62
|
+
--ol-surface-active: var(--openleaf-color-surface-active, #1f3a5f);
|
|
63
|
+
--ol-border: var(--openleaf-color-border, #3d444d);
|
|
64
|
+
--ol-accent: var(--openleaf-color-accent, #79c0ff);
|
|
65
|
+
--ol-focus: var(--openleaf-color-focus, #79c0ff);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.ol-editor[data-ol-scheme="dark"] {
|
|
69
|
+
--ol-text: var(--openleaf-color-text, #e6edf3);
|
|
70
|
+
--ol-text-muted: var(--openleaf-color-text-muted, #9198a1);
|
|
71
|
+
--ol-surface: var(--openleaf-color-surface, #0d1117);
|
|
72
|
+
--ol-surface-hover: var(--openleaf-color-surface-hover, #21262d);
|
|
73
|
+
--ol-surface-active: var(--openleaf-color-surface-active, #1f3a5f);
|
|
74
|
+
--ol-border: var(--openleaf-color-border, #3d444d);
|
|
75
|
+
--ol-accent: var(--openleaf-color-accent, #79c0ff);
|
|
76
|
+
--ol-focus: var(--openleaf-color-focus, #79c0ff);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* Native widget chrome -- the popup a `select` opens, scrollbars, the caret,
|
|
80
|
+
form control backgrounds -- is painted by the browser from `color-scheme`,
|
|
81
|
+
not from any property we can hand an integrator. Left alone it follows the
|
|
82
|
+
page, which is right while the editor's own palette also follows the page and
|
|
83
|
+
wrong the moment either attribute pins the editor to one world. So it is set
|
|
84
|
+
exactly when the editor stops following along, and inherited from the host
|
|
85
|
+
otherwise. */
|
|
86
|
+
.ol-editor[data-ol-theme="light"] { color-scheme: light; }
|
|
87
|
+
.ol-editor[data-ol-theme="dark"] { color-scheme: dark; }
|
|
88
|
+
.ol-editor[data-ol-scheme="light"] { color-scheme: light; }
|
|
89
|
+
.ol-editor[data-ol-scheme="dark"] { color-scheme: dark; }
|
|
90
|
+
|
|
91
|
+
/* Wrapping, not scrolling and not an overflow menu. Both of those hide
|
|
92
|
+
controls -- one off-screen, one behind a click -- and a formatting control
|
|
93
|
+
the author cannot see is a control they do not have. */
|
|
94
|
+
.ol-editor .ol-toolbar {
|
|
95
|
+
display: flex;
|
|
96
|
+
flex-wrap: wrap;
|
|
97
|
+
align-items: center;
|
|
98
|
+
gap: var(--ol-gap);
|
|
99
|
+
box-sizing: border-box;
|
|
100
|
+
margin: 0;
|
|
101
|
+
padding: 4px;
|
|
102
|
+
border: 1px solid var(--ol-border);
|
|
103
|
+
border-bottom: 0;
|
|
104
|
+
border-radius: var(--ol-radius) var(--ol-radius) 0 0;
|
|
105
|
+
background: var(--ol-surface);
|
|
106
|
+
font: inherit;
|
|
107
|
+
position: relative;
|
|
108
|
+
z-index: var(--ol-z);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.ol-editor .ol-group {
|
|
112
|
+
display: flex;
|
|
113
|
+
flex-wrap: wrap;
|
|
114
|
+
align-items: center;
|
|
115
|
+
gap: var(--ol-gap);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* The divider is a BORDER on the group, not a standalone flex item.
|
|
119
|
+
As its own element it could wrap onto the trailing edge of a row with nothing
|
|
120
|
+
after it -- a stranded line floating at the end of a row. Wrapping begins
|
|
121
|
+
around 690-740px, well inside ordinary desktop widths (a CMS sidebar, a
|
|
122
|
+
half-width split pane), so this was not a 360px-only edge case.
|
|
123
|
+
border-inline-start rather than border-left, so it flips under RTL. */
|
|
124
|
+
.ol-editor .ol-group + .ol-group {
|
|
125
|
+
border-inline-start: 1px solid var(--ol-border);
|
|
126
|
+
padding-inline-start: 5px;
|
|
127
|
+
margin-inline-start: 1px;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* Every property a host reset or `button {}` rule is likely to touch is set
|
|
131
|
+
here explicitly. Specificity is (0,2,0) via the descendant selector, which
|
|
132
|
+
beats a bare element or single-class host rule without resorting to
|
|
133
|
+
!important. */
|
|
134
|
+
.ol-editor .ol-btn {
|
|
135
|
+
box-sizing: border-box;
|
|
136
|
+
display: inline-flex;
|
|
137
|
+
align-items: center;
|
|
138
|
+
justify-content: center;
|
|
139
|
+
flex: 0 0 auto;
|
|
140
|
+
width: var(--ol-button-size);
|
|
141
|
+
height: var(--ol-button-size);
|
|
142
|
+
min-width: var(--ol-button-size);
|
|
143
|
+
min-height: var(--ol-button-size);
|
|
144
|
+
max-width: none;
|
|
145
|
+
padding: 0;
|
|
146
|
+
margin: 0;
|
|
147
|
+
border: 1px solid transparent;
|
|
148
|
+
border-radius: var(--ol-radius);
|
|
149
|
+
background: transparent;
|
|
150
|
+
color: var(--ol-text);
|
|
151
|
+
font: inherit;
|
|
152
|
+
font-family: var(--ol-font);
|
|
153
|
+
font-size: var(--ol-font-size);
|
|
154
|
+
font-weight: 400;
|
|
155
|
+
line-height: 1;
|
|
156
|
+
letter-spacing: normal;
|
|
157
|
+
text-transform: none;
|
|
158
|
+
text-align: center;
|
|
159
|
+
text-decoration: none;
|
|
160
|
+
text-shadow: none;
|
|
161
|
+
box-shadow: none;
|
|
162
|
+
opacity: 1;
|
|
163
|
+
cursor: pointer;
|
|
164
|
+
appearance: none;
|
|
165
|
+
-webkit-appearance: none;
|
|
166
|
+
outline-offset: var(--ol-focus-offset);
|
|
167
|
+
-webkit-tap-highlight-color: transparent;
|
|
168
|
+
transition: background-color 120ms ease, border-color 120ms ease;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.ol-editor .ol-btn:hover {
|
|
172
|
+
background: var(--ol-surface-hover);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/* :focus-visible only, so a mouse click does not leave a ring behind, but the
|
|
176
|
+
ring is never removed for keyboard users. */
|
|
177
|
+
.ol-editor .ol-btn:focus-visible {
|
|
178
|
+
outline: var(--ol-focus-width) solid var(--ol-focus);
|
|
179
|
+
outline-offset: var(--ol-focus-offset);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/* Pressed state is distinguished from hover by THREE signals, not just colour:
|
|
183
|
+
a filled background, a visible border, and an inset shadow that reads as
|
|
184
|
+
physically depressed. Colour alone would fail for a colour-blind author and
|
|
185
|
+
would be invisible in forced-colours mode. */
|
|
186
|
+
.ol-editor .ol-btn[aria-pressed="true"] {
|
|
187
|
+
background: var(--ol-surface-active);
|
|
188
|
+
border-color: var(--ol-accent);
|
|
189
|
+
color: var(--ol-accent);
|
|
190
|
+
box-shadow: inset 0 1px 2px rgb(0 0 0 / 12%);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.ol-editor .ol-btn[aria-pressed="true"]:hover {
|
|
194
|
+
background: var(--ol-surface-active);
|
|
195
|
+
border-color: var(--ol-accent);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/* aria-disabled rather than the disabled attribute: a disabled button is
|
|
199
|
+
removed from the roving tabindex and cannot be reached or announced, so an
|
|
200
|
+
author using a screen reader cannot discover that the control exists. */
|
|
201
|
+
.ol-editor .ol-btn[aria-disabled="true"] {
|
|
202
|
+
opacity: 0.4;
|
|
203
|
+
cursor: default;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.ol-editor .ol-btn[aria-disabled="true"]:hover {
|
|
207
|
+
background: transparent;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.ol-editor .ol-icon {
|
|
211
|
+
width: var(--ol-icon-size);
|
|
212
|
+
height: var(--ol-icon-size);
|
|
213
|
+
display: block;
|
|
214
|
+
pointer-events: none;
|
|
215
|
+
flex: 0 0 auto;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/* Block-type select. Sized and coloured to sit level with the icon buttons so
|
|
219
|
+
it does not read as bolted on, but it stays a native <select> -- a custom
|
|
220
|
+
listbox is a large amount of ARIA that would then owe real screen reader
|
|
221
|
+
testing to be worth anything. */
|
|
222
|
+
.ol-editor .ol-select {
|
|
223
|
+
box-sizing: border-box;
|
|
224
|
+
height: var(--ol-button-size);
|
|
225
|
+
max-width: 11em;
|
|
226
|
+
padding: 0 22px 0 6px;
|
|
227
|
+
margin: 0;
|
|
228
|
+
border: 1px solid var(--ol-border);
|
|
229
|
+
border-radius: var(--ol-radius);
|
|
230
|
+
background-color: transparent;
|
|
231
|
+
background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
|
|
232
|
+
linear-gradient(135deg, currentColor 50%, transparent 50%);
|
|
233
|
+
background-position: right 10px center, right 6px center;
|
|
234
|
+
background-size: 4px 4px, 4px 4px;
|
|
235
|
+
background-repeat: no-repeat;
|
|
236
|
+
color: var(--ol-text);
|
|
237
|
+
font-family: var(--ol-font);
|
|
238
|
+
font-size: var(--ol-font-size);
|
|
239
|
+
font-weight: 400;
|
|
240
|
+
line-height: 1;
|
|
241
|
+
text-transform: none;
|
|
242
|
+
letter-spacing: normal;
|
|
243
|
+
box-shadow: none;
|
|
244
|
+
cursor: pointer;
|
|
245
|
+
appearance: none;
|
|
246
|
+
-webkit-appearance: none;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.ol-editor .ol-select:hover {
|
|
250
|
+
background-color: var(--ol-surface-hover);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.ol-editor .ol-select:focus-visible {
|
|
254
|
+
outline: var(--ol-focus-width) solid var(--ol-focus);
|
|
255
|
+
outline-offset: var(--ol-focus-offset);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/* The option list is painted by the OS, which does not inherit our tokens, so
|
|
259
|
+
give it explicit colours or dark mode shows black text on black. */
|
|
260
|
+
.ol-editor .ol-select option {
|
|
261
|
+
background: var(--ol-surface);
|
|
262
|
+
color: var(--ol-text);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.ol-editor .ol-content {
|
|
266
|
+
box-sizing: border-box;
|
|
267
|
+
border: 1px solid var(--ol-border);
|
|
268
|
+
border-radius: 0 0 var(--ol-radius) var(--ol-radius);
|
|
269
|
+
background: var(--ol-surface);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/* Deliberately minimal: the content area inherits the host's typography, which
|
|
273
|
+
is the entire reason this project does not use Shadow DOM. Only padding and
|
|
274
|
+
the focus ring are ours. */
|
|
275
|
+
.ol-editor .ol-content .ProseMirror {
|
|
276
|
+
padding: 12px;
|
|
277
|
+
min-height: 8rem;
|
|
278
|
+
outline: none;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.ol-editor .ol-content:focus-within {
|
|
282
|
+
outline: 2px solid var(--ol-focus);
|
|
283
|
+
outline-offset: -1px;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/* Tables.
|
|
287
|
+
These styles live in core, not in the opt-in table plugin, because table
|
|
288
|
+
NODES live in core: every deployment reads and renders tables even where
|
|
289
|
+
editing them is switched off. Unstyled tables would render as runs of
|
|
290
|
+
undelimited text. */
|
|
291
|
+
.ol-editor .ol-content .ProseMirror table {
|
|
292
|
+
border-collapse: collapse;
|
|
293
|
+
table-layout: fixed;
|
|
294
|
+
width: 100%;
|
|
295
|
+
margin: 0 0 1em;
|
|
296
|
+
overflow: hidden;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.ol-editor .ol-content .ProseMirror td,
|
|
300
|
+
.ol-editor .ol-content .ProseMirror th {
|
|
301
|
+
border: 1px solid var(--ol-border);
|
|
302
|
+
padding: 6px 8px;
|
|
303
|
+
vertical-align: top;
|
|
304
|
+
/* A zero-width cell cannot be clicked into, so it cannot be repaired. */
|
|
305
|
+
min-width: 2em;
|
|
306
|
+
position: relative;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.ol-editor .ol-content .ProseMirror th {
|
|
310
|
+
background: var(--ol-surface-hover);
|
|
311
|
+
font-weight: 600;
|
|
312
|
+
text-align: start;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/* prosemirror-tables marks cells in a rectangular selection with this class.
|
|
316
|
+
An ::after overlay rather than a background so it composes with a cell that
|
|
317
|
+
already has one, and so a header cell still reads as a header. */
|
|
318
|
+
.ol-editor .ol-content .ProseMirror .selectedCell::after {
|
|
319
|
+
content: "";
|
|
320
|
+
position: absolute;
|
|
321
|
+
inset: 0;
|
|
322
|
+
background: var(--ol-surface-active);
|
|
323
|
+
opacity: 0.4;
|
|
324
|
+
pointer-events: none;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/* The column resize handle, drawn by prosemirror-tables' columnResizing. */
|
|
328
|
+
.ol-editor .ol-content .ProseMirror .column-resize-handle {
|
|
329
|
+
position: absolute;
|
|
330
|
+
right: -2px;
|
|
331
|
+
top: 0;
|
|
332
|
+
bottom: 0;
|
|
333
|
+
width: 4px;
|
|
334
|
+
background: var(--ol-accent);
|
|
335
|
+
pointer-events: none;
|
|
336
|
+
z-index: 20;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.ol-editor .ol-content .ProseMirror.resize-cursor {
|
|
340
|
+
cursor: col-resize;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/* Preserved-but-unrecognised markup, surfaced rather than hidden. */
|
|
344
|
+
.ol-editor .ol-content .ProseMirror-selectednode {
|
|
345
|
+
outline: 2px solid var(--ol-focus);
|
|
346
|
+
outline-offset: 2px;
|
|
347
|
+
border-radius: 2px;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.ol-editor .ol-source {
|
|
351
|
+
box-sizing: border-box;
|
|
352
|
+
display: block;
|
|
353
|
+
width: 100%;
|
|
354
|
+
min-height: 12rem;
|
|
355
|
+
padding: 12px;
|
|
356
|
+
border: 1px solid var(--ol-border);
|
|
357
|
+
border-radius: 0 0 var(--ol-radius) var(--ol-radius);
|
|
358
|
+
background: var(--ol-surface);
|
|
359
|
+
color: var(--ol-text);
|
|
360
|
+
font-family: var(--ol-font-mono);
|
|
361
|
+
font-size: 13px;
|
|
362
|
+
line-height: 1.5;
|
|
363
|
+
resize: vertical;
|
|
364
|
+
white-space: pre-wrap;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/* Directional icons under RTL.
|
|
368
|
+
Group order reverses for free with flexbox, but a curved undo arrow does not:
|
|
369
|
+
in an RTL document "back" points the other way. Only genuinely directional
|
|
370
|
+
icons are flipped -- bold and italic must not be mirrored. */
|
|
371
|
+
.ol-editor[dir="rtl"] .ol-icon--directional,
|
|
372
|
+
[dir="rtl"] .ol-editor .ol-icon--directional {
|
|
373
|
+
transform: scaleX(-1);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/* The announcement region. Visually hidden but not display:none, which would
|
|
377
|
+
remove it from the accessibility tree and silence it. */
|
|
378
|
+
.ol-editor .ol-live {
|
|
379
|
+
position: absolute;
|
|
380
|
+
width: 1px;
|
|
381
|
+
height: 1px;
|
|
382
|
+
margin: -1px;
|
|
383
|
+
padding: 0;
|
|
384
|
+
overflow: hidden;
|
|
385
|
+
clip: rect(0 0 0 0);
|
|
386
|
+
clip-path: inset(50%);
|
|
387
|
+
white-space: nowrap;
|
|
388
|
+
border: 0;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/* Coarse pointers get a larger target. WCAG 2.2 SC 2.5.8 asks for 24x24 CSS px
|
|
392
|
+
minimum; 32 clears that on a mouse and 40 is comfortable on a thumb. */
|
|
393
|
+
@media (pointer: coarse) {
|
|
394
|
+
.ol-editor {
|
|
395
|
+
--ol-button-size: var(--openleaf-button-size, 40px);
|
|
396
|
+
--ol-gap: var(--openleaf-gap, 4px);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
@media (prefers-reduced-motion: reduce) {
|
|
401
|
+
.ol-editor .ol-btn {
|
|
402
|
+
transition: none;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/* Forced colours (Windows high contrast) replaces our palette wholesale. The
|
|
407
|
+
pressed state must not depend on a background we no longer control, so it is
|
|
408
|
+
re-expressed as a border, which the mode preserves. */
|
|
409
|
+
@media (forced-colors: active) {
|
|
410
|
+
.ol-editor .ol-btn {
|
|
411
|
+
border-color: ButtonBorder;
|
|
412
|
+
color: ButtonText;
|
|
413
|
+
}
|
|
414
|
+
.ol-editor .ol-btn[aria-pressed="true"] {
|
|
415
|
+
border-color: Highlight;
|
|
416
|
+
color: Highlight;
|
|
417
|
+
box-shadow: none;
|
|
418
|
+
}
|
|
419
|
+
.ol-editor .ol-btn:focus-visible {
|
|
420
|
+
outline-color: Highlight;
|
|
421
|
+
}
|
|
422
|
+
.ol-editor .ol-btn[aria-disabled="true"] {
|
|
423
|
+
color: GrayText;
|
|
424
|
+
opacity: 1;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The toolbar item registry.
|
|
3
|
+
*
|
|
4
|
+
* The extension point exists now, before any plugin needs it, because
|
|
5
|
+
* retrofitting one later means either a breaking change or a second parallel
|
|
6
|
+
* mechanism. `@openleaf-editor/plugins-table` will call `registerToolbarItem` at import
|
|
7
|
+
* time and the toolbar will pick it up without ever importing the plugin.
|
|
8
|
+
*
|
|
9
|
+
* Two responsibilities are deliberately split:
|
|
10
|
+
*
|
|
11
|
+
* A plugin declares CAPABILITY -- here is a button, here is what it does,
|
|
12
|
+
* here is when it is active.
|
|
13
|
+
* An integrator declares LAYOUT -- via the `toolbar` attribute, which names
|
|
14
|
+
* item ids and separators in the order they want them.
|
|
15
|
+
*
|
|
16
|
+
* So installing a plugin never silently rearranges somebody's toolbar, and an
|
|
17
|
+
* integrator can omit a registered item without uninstalling anything.
|
|
18
|
+
*/
|
|
19
|
+
import type { Command, EditorState } from 'prosemirror-state';
|
|
20
|
+
import type { EditorView } from 'prosemirror-view';
|
|
21
|
+
import type { IconName } from './icons.js';
|
|
22
|
+
/** What a custom `run` handler receives. */
|
|
23
|
+
export interface ToolbarContext {
|
|
24
|
+
view: EditorView;
|
|
25
|
+
/** The `<openleaf-editor>` element, for dispatching events or hosting dialogs. */
|
|
26
|
+
host: HTMLElement;
|
|
27
|
+
}
|
|
28
|
+
export interface ToolbarItemSpec {
|
|
29
|
+
/** Stable id used in the `toolbar` layout attribute. */
|
|
30
|
+
id: string;
|
|
31
|
+
/**
|
|
32
|
+
* What kind of control this is.
|
|
33
|
+
*
|
|
34
|
+
* Present from day one even though **only `button` is implemented** -- the
|
|
35
|
+
* block-type control is special-cased by id, not by type. A flat spec models
|
|
36
|
+
* a button and nothing else, and adding this discriminant *after* the config
|
|
37
|
+
* shape is public is precisely the breaking change the registry exists to
|
|
38
|
+
* avoid. Colour grids, table-insert popovers and link editors will all be
|
|
39
|
+
* `custom`.
|
|
40
|
+
*
|
|
41
|
+
* Declaring an unimplemented variant logs a warning rather than failing
|
|
42
|
+
* quietly as a button.
|
|
43
|
+
*/
|
|
44
|
+
type?: 'button' | 'select' | 'custom';
|
|
45
|
+
/** Accessible name. Kept constant across states -- the platform announces pressed. */
|
|
46
|
+
label: string;
|
|
47
|
+
icon?: IconName;
|
|
48
|
+
/**
|
|
49
|
+
* `toggle` gets `aria-pressed` and reflects `isActive`. `action` does not:
|
|
50
|
+
* marking Undo as "pressed" is meaningless and screen readers say so.
|
|
51
|
+
*/
|
|
52
|
+
kind?: 'toggle' | 'action';
|
|
53
|
+
/** A plain ProseMirror command. Its no-dispatch call also drives enabled state. */
|
|
54
|
+
command?: Command;
|
|
55
|
+
/** For items needing more than the editor state -- dialogs, mode switches. */
|
|
56
|
+
run?: (ctx: ToolbarContext) => void;
|
|
57
|
+
isActive?: (state: EditorState) => boolean;
|
|
58
|
+
/** Defaults to asking `command` whether it would apply. */
|
|
59
|
+
isEnabled?: (state: EditorState) => boolean;
|
|
60
|
+
/** Label to look up in the core shortcut table, for the tooltip. */
|
|
61
|
+
shortcut?: string;
|
|
62
|
+
}
|
|
63
|
+
export declare function onRegistryChange(listener: () => void): () => void;
|
|
64
|
+
/**
|
|
65
|
+
* Register a toolbar item. Last registration for an id wins, which lets an
|
|
66
|
+
* integrator replace a built-in (a different link dialog, say) without forking.
|
|
67
|
+
*/
|
|
68
|
+
export declare function registerToolbarItem(spec: ToolbarItemSpec): void;
|
|
69
|
+
export declare function getToolbarItem(id: string): ToolbarItemSpec | undefined;
|
|
70
|
+
export declare function allToolbarItems(): ToolbarItemSpec[];
|
|
71
|
+
/** Testing seam. Not part of the public API. */
|
|
72
|
+
export declare function clearToolbarItems(): void;
|
|
73
|
+
/**
|
|
74
|
+
* The default layout. `|` is a separator.
|
|
75
|
+
*
|
|
76
|
+
* Order reasoning: history sits leftmost because it is the control an author
|
|
77
|
+
* reaches for under stress and muscle memory puts it at the start of the bar in
|
|
78
|
+
* every office application. Block type comes next because choosing what a
|
|
79
|
+
* paragraph *is* precedes decorating it. Then character marks, then block
|
|
80
|
+
* structure, then insertions, and finally source view -- the one control that
|
|
81
|
+
* changes the editor's mode rather than the document, kept away from the rest
|
|
82
|
+
* so it is not hit by accident.
|
|
83
|
+
*/
|
|
84
|
+
export declare const DEFAULT_LAYOUT: string;
|
|
85
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC7D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAE1C,4CAA4C;AAC5C,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,UAAU,CAAA;IAChB,kFAAkF;IAClF,IAAI,EAAE,WAAW,CAAA;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,wDAAwD;IACxD,EAAE,EAAE,MAAM,CAAA;IACV;;;;;;;;;;;;OAYG;IACH,IAAI,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAA;IACrC,sFAAsF;IACtF,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,QAAQ,CAAA;IACf;;;OAGG;IACH,IAAI,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAA;IAC1B,mFAAmF;IACnF,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,8EAA8E;IAC9E,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,IAAI,CAAA;IACnC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,OAAO,CAAA;IAC1C,2DAA2D;IAC3D,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,OAAO,CAAA;IAC3C,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAcD,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAGjE;AAcD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAG/D;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAEtE;AAED,wBAAgB,eAAe,IAAI,eAAe,EAAE,CAEnD;AAED,gDAAgD;AAChD,wBAAgB,iBAAiB,IAAI,IAAI,CAGxC;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,cAAc,QAEgE,CAAA"}
|
package/dist/registry.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The toolbar item registry.
|
|
3
|
+
*
|
|
4
|
+
* The extension point exists now, before any plugin needs it, because
|
|
5
|
+
* retrofitting one later means either a breaking change or a second parallel
|
|
6
|
+
* mechanism. `@openleaf-editor/plugins-table` will call `registerToolbarItem` at import
|
|
7
|
+
* time and the toolbar will pick it up without ever importing the plugin.
|
|
8
|
+
*
|
|
9
|
+
* Two responsibilities are deliberately split:
|
|
10
|
+
*
|
|
11
|
+
* A plugin declares CAPABILITY -- here is a button, here is what it does,
|
|
12
|
+
* here is when it is active.
|
|
13
|
+
* An integrator declares LAYOUT -- via the `toolbar` attribute, which names
|
|
14
|
+
* item ids and separators in the order they want them.
|
|
15
|
+
*
|
|
16
|
+
* So installing a plugin never silently rearranges somebody's toolbar, and an
|
|
17
|
+
* integrator can omit a registered item without uninstalling anything.
|
|
18
|
+
*/
|
|
19
|
+
const registry = new Map();
|
|
20
|
+
/**
|
|
21
|
+
* Registry change notifications.
|
|
22
|
+
*
|
|
23
|
+
* Import-time registration races code-split plugins: if a lazily loaded chunk
|
|
24
|
+
* resolves after a toolbar has already read the registry and rendered, its
|
|
25
|
+
* button would silently never appear. Toolbars subscribe and re-render instead
|
|
26
|
+
* of reading once at mount.
|
|
27
|
+
*/
|
|
28
|
+
const listeners = new Set();
|
|
29
|
+
export function onRegistryChange(listener) {
|
|
30
|
+
listeners.add(listener);
|
|
31
|
+
return () => listeners.delete(listener);
|
|
32
|
+
}
|
|
33
|
+
function notify() {
|
|
34
|
+
// Isolated per listener: one toolbar failing to re-render must not stop the
|
|
35
|
+
// others, and must not propagate into the plugin that registered the item.
|
|
36
|
+
for (const listener of listeners) {
|
|
37
|
+
try {
|
|
38
|
+
listener();
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
console.error('@openleaf-editor/ui: a toolbar failed to re-render', error);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Register a toolbar item. Last registration for an id wins, which lets an
|
|
47
|
+
* integrator replace a built-in (a different link dialog, say) without forking.
|
|
48
|
+
*/
|
|
49
|
+
export function registerToolbarItem(spec) {
|
|
50
|
+
registry.set(spec.id, spec);
|
|
51
|
+
notify();
|
|
52
|
+
}
|
|
53
|
+
export function getToolbarItem(id) {
|
|
54
|
+
return registry.get(id);
|
|
55
|
+
}
|
|
56
|
+
export function allToolbarItems() {
|
|
57
|
+
return [...registry.values()];
|
|
58
|
+
}
|
|
59
|
+
/** Testing seam. Not part of the public API. */
|
|
60
|
+
export function clearToolbarItems() {
|
|
61
|
+
registry.clear();
|
|
62
|
+
notify();
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The default layout. `|` is a separator.
|
|
66
|
+
*
|
|
67
|
+
* Order reasoning: history sits leftmost because it is the control an author
|
|
68
|
+
* reaches for under stress and muscle memory puts it at the start of the bar in
|
|
69
|
+
* every office application. Block type comes next because choosing what a
|
|
70
|
+
* paragraph *is* precedes decorating it. Then character marks, then block
|
|
71
|
+
* structure, then insertions, and finally source view -- the one control that
|
|
72
|
+
* changes the editor's mode rather than the document, kept away from the rest
|
|
73
|
+
* so it is not hit by accident.
|
|
74
|
+
*/
|
|
75
|
+
export const DEFAULT_LAYOUT = 'undo redo | blockType | bold italic underline strikethrough code | ' +
|
|
76
|
+
'bulletList orderedList blockquote codeBlock | link unlink image horizontalRule | source';
|
|
77
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAiDH,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA2B,CAAA;AAEnD;;;;;;;GAOG;AACH,MAAM,SAAS,GAAG,IAAI,GAAG,EAAc,CAAA;AAEvC,MAAM,UAAU,gBAAgB,CAAC,QAAoB;IACnD,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;IACvB,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;AACzC,CAAC;AAED,SAAS,MAAM;IACb,4EAA4E;IAC5E,2EAA2E;IAC3E,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,IAAI,CAAC;YACH,QAAQ,EAAE,CAAA;QACZ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,oDAAoD,EAAE,KAAK,CAAC,CAAA;QAC5E,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAqB;IACvD,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAA;IAC3B,MAAM,EAAE,CAAA;AACV,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,EAAU;IACvC,OAAO,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;AACzB,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,OAAO,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;AAC/B,CAAC;AAED,gDAAgD;AAChD,MAAM,UAAU,iBAAiB;IAC/B,QAAQ,CAAC,KAAK,EAAE,CAAA;IAChB,MAAM,EAAE,CAAA;AACV,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,cAAc,GACzB,qEAAqE;IACrE,yFAAyF,CAAA"}
|