@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/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 = `rk-find-${++counter}`;
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-rk-tip", label);
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-rk-tip", message(key));
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("rk-find");
86
- highlights.delete("rk-find-current");
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("rk-find", new Highlight(...ranges));
100
+ highlights.set("rte-find", new Highlight(...ranges));
101
101
  if (current)
102
- highlights.set("rk-find-current", new Highlight(current));
102
+ highlights.set("rte-find-current", new Highlight(current));
103
103
  else
104
- highlights.delete("rk-find-current");
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("rk-find");
183
- highlights?.delete("rk-find-current");
182
+ highlights?.delete("rte-find");
183
+ highlights?.delete("rte-find-current");
184
184
  bar.remove();
185
185
  },
186
186
  };
@@ -0,0 +1,24 @@
1
+ import type { UiItem } from "@rathkit/rte-core";
2
+ import type { UiEditor } from "./types.ts";
3
+ export declare const FLOATING_SLOT = "toolbar.floating";
4
+ export declare const CONTEXT_SLOT = "menu.context";
5
+ type Dialog = Extract<UiItem, {
6
+ kind: "dialog";
7
+ }>;
8
+ export interface FloatingOptions {
9
+ readonly editor: UiEditor;
10
+ readonly root: HTMLElement;
11
+ readonly selectionToolbar: boolean;
12
+ readonly contextMenu: boolean;
13
+ readonly part: <E extends HTMLElement>(el: E, name: string) => E;
14
+ readonly message: (key: string, fallback?: string) => string;
15
+ readonly icon: (name: string | undefined) => string | (() => Node) | undefined;
16
+ readonly openDialog: (item: Dialog, opener: HTMLElement) => void;
17
+ }
18
+ export interface Floating {
19
+ /** Words or icons may have changed: draw again. */
20
+ refresh(): void;
21
+ destroy(): void;
22
+ }
23
+ export declare function createFloating(options: FloatingOptions): Floating;
24
+ export {};
@@ -0,0 +1,286 @@
1
+ import { createMenu, createToolbar } from "@rathkit/rte-headless";
2
+ import { setIcon } from "./icon.js";
3
+ export const FLOATING_SLOT = "toolbar.floating";
4
+ export const CONTEXT_SLOT = "menu.context";
5
+ function copyItems(editor, ids) {
6
+ const main = editor.ui.items("toolbar.main");
7
+ const out = [];
8
+ ids.forEach(([id, group], index) => {
9
+ const found = main.find((i) => i.id === id);
10
+ if (found && found.kind !== "menu" && found.kind !== "separator" && found.kind !== "select" && found.kind !== "panel")
11
+ out.push({ ...found, group, order: index });
12
+ });
13
+ return out;
14
+ }
15
+ const SELECTION_DEFAULTS = [["bold", "text"], ["italic", "text"], ["underline", "text"], ["strike", "text"], ["code", "text"], ["link", "link"], ["unlink", "link"], ["clear-format", "clear"]];
16
+ const CONTEXT_DEFAULTS = [["undo", "history"], ["redo", "history"], ["bold", "text"], ["italic", "text"], ["underline", "text"], ["strike", "text"], ["code", "text"], ["link", "link"], ["unlink", "link"], ["clear-format", "clear"]];
17
+ let counter = 0;
18
+ export function createFloating(options) {
19
+ const { editor, root, part, message } = options;
20
+ const stops = [];
21
+ const redraws = [];
22
+ const seed = (slot, ids) => {
23
+ if (editor.ui.items(slot).length > 0)
24
+ return;
25
+ const removers = copyItems(editor, ids).map((item) => editor.ui.add(slot, item));
26
+ stops.push(() => removers.forEach((remove) => remove()));
27
+ };
28
+ const returnTo = document.createElement("button");
29
+ returnTo.type = "button";
30
+ returnTo.hidden = true;
31
+ returnTo.focus = () => editor.focus();
32
+ root.appendChild(returnTo);
33
+ stops.push(() => returnTo.remove());
34
+ const iconInto = (el, name) => {
35
+ const holder = part(document.createElement("span"), "icon");
36
+ holder.setAttribute("aria-hidden", "true");
37
+ if (!setIcon(holder, options.icon(name)))
38
+ return false;
39
+ el.appendChild(holder);
40
+ return true;
41
+ };
42
+ if (options.selectionToolbar) {
43
+ seed(FLOATING_SLOT, SELECTION_DEFAULTS);
44
+ const toolbar = createToolbar(editor, { slot: FLOATING_SLOT, label: message("floating.label") });
45
+ const bar = part(document.createElement("div"), "floating");
46
+ bar.hidden = true;
47
+ root.appendChild(bar);
48
+ const draw = () => {
49
+ const controls = [];
50
+ let group;
51
+ for (const item of toolbar.items) {
52
+ if (item.kind !== "button" && item.kind !== "toggle" && item.kind !== "dialog")
53
+ continue;
54
+ const next = item.group ?? "";
55
+ if (group !== undefined && next !== group)
56
+ controls.push(part(document.createElement("span"), "separator"));
57
+ group = next;
58
+ const button = part(document.createElement("button"), "button");
59
+ button.type = "button";
60
+ button.setAttribute("data-rte-item", item.id);
61
+ const state = toolbar.state(item.id);
62
+ const label = message(`item.${item.id}`, state?.label ?? item.id);
63
+ button.setAttribute("aria-label", label);
64
+ button.setAttribute("data-rte-tip", state?.shortcutLabel ? `${label} (${state.shortcutLabel})` : label);
65
+ if (!iconInto(button, state?.icon)) {
66
+ const words = part(document.createElement("span"), "label");
67
+ words.textContent = label;
68
+ button.appendChild(words);
69
+ }
70
+ controls.push(button);
71
+ }
72
+ bar.replaceChildren(...controls);
73
+ unbind?.();
74
+ unbind = toolbar.bind(bar, { onOpen: (item) => { if (item.kind === "dialog")
75
+ options.openDialog(item, returnTo); } });
76
+ };
77
+ let unbind;
78
+ draw();
79
+ const stopItems = toolbar.subscribe(() => {
80
+ const drawn = [...bar.querySelectorAll("[data-rte-item]")].map((el) => el.getAttribute("data-rte-item")).join("\u0000");
81
+ if (drawn !== toolbar.items.filter((i) => i.kind === "button" || i.kind === "toggle" || i.kind === "dialog").map((i) => i.id).join("\u0000"))
82
+ draw();
83
+ });
84
+ let pointerDown = false;
85
+ let touched = false;
86
+ let dismissedFor = "";
87
+ let timer;
88
+ const focused = () => {
89
+ const active = root.getRootNode().activeElement;
90
+ return !!active && (editor.element.contains(active) || bar.contains(active));
91
+ };
92
+ const selectionKey = () => {
93
+ const { anchor, focus } = editor.snapshot.selection;
94
+ return `${anchor.path.join()}:${anchor.offset}-${focus.path.join()}:${focus.offset}`;
95
+ };
96
+ const hasSelection = () => {
97
+ const s = editor.snapshot.selection;
98
+ return s.anchor.offset !== s.focus.offset || s.anchor.path.join() !== s.focus.path.join();
99
+ };
100
+ const place = () => {
101
+ const selection = root.ownerDocument.getSelection();
102
+ const range = selection && selection.rangeCount > 0 ? selection.getRangeAt(0) : null;
103
+ const rect = range?.getBoundingClientRect();
104
+ if (!rect || (rect.width === 0 && rect.height === 0)) {
105
+ bar.hidden = true;
106
+ return;
107
+ }
108
+ const box = root.getBoundingClientRect();
109
+ const left0 = box.left + root.clientLeft;
110
+ const top0 = box.top + root.clientTop;
111
+ bar.hidden = false;
112
+ const above = rect.top - top0 - bar.offsetHeight - 8;
113
+ const top = above >= 0 ? above : rect.bottom - top0 + 8;
114
+ const left = Math.max(0, Math.min(rect.left + rect.width / 2 - left0 - bar.offsetWidth / 2, root.clientWidth - bar.offsetWidth));
115
+ bar.style.top = `${Math.round(top)}px`;
116
+ bar.style.left = `${Math.round(left)}px`;
117
+ };
118
+ const update = () => {
119
+ timer = undefined;
120
+ const show = hasSelection() && !pointerDown && !touched && dismissedFor !== selectionKey() && focused();
121
+ if (show)
122
+ place();
123
+ else if (!bar.contains(root.getRootNode().activeElement))
124
+ bar.hidden = true;
125
+ };
126
+ const schedule = () => { timer ??= setTimeout(update, 80); };
127
+ stops.push(editor.subscribe(schedule));
128
+ const on = (target, type, handler, capture = false) => {
129
+ target.addEventListener(type, handler, capture);
130
+ stops.push(() => target.removeEventListener(type, handler, capture));
131
+ };
132
+ on(editor.element, "pointerdown", (e) => { pointerDown = true; touched = e.pointerType === "touch"; if (!bar.hidden && !bar.contains(e.target))
133
+ bar.hidden = true; });
134
+ on(root.ownerDocument, "pointerup", () => { if (pointerDown) {
135
+ pointerDown = false;
136
+ schedule();
137
+ } }, true);
138
+ on(root.ownerDocument, "pointercancel", () => { pointerDown = false; }, true);
139
+ on(editor.element, "focusout", (e) => { if (!bar.contains(e.relatedTarget))
140
+ schedule(); });
141
+ on(bar, "focusout", (e) => { if (!editor.element.contains(e.relatedTarget) && !bar.contains(e.relatedTarget))
142
+ schedule(); });
143
+ on(root.ownerDocument.defaultView ?? window, "scroll", () => { if (!bar.hidden)
144
+ place(); }, true);
145
+ on(root.ownerDocument.defaultView ?? window, "resize", () => { if (!bar.hidden)
146
+ place(); });
147
+ on(editor.element, "keydown", (e) => {
148
+ touched = false;
149
+ if (e.key === "F10" && e.altKey && !bar.hidden) {
150
+ e.preventDefault();
151
+ bar.querySelector("[data-rte-item][tabindex='0']")?.focus();
152
+ }
153
+ });
154
+ on(bar, "keydown", (e) => { if (e.key === "Escape") {
155
+ e.preventDefault();
156
+ dismissedFor = selectionKey();
157
+ bar.hidden = true;
158
+ editor.focus();
159
+ } });
160
+ redraws.push(draw);
161
+ stops.push(() => { stopItems(); unbind?.(); toolbar.destroy(); if (timer)
162
+ clearTimeout(timer); bar.remove(); });
163
+ }
164
+ if (options.contextMenu) {
165
+ seed(CONTEXT_SLOT, CONTEXT_DEFAULTS);
166
+ const toolbar = createToolbar(editor, { slot: CONTEXT_SLOT });
167
+ const popup = part(document.createElement("div"), "menu");
168
+ popup.setAttribute("data-rte-context", "");
169
+ const anchor = document.createElement("button");
170
+ anchor.type = "button";
171
+ anchor.hidden = true;
172
+ anchor.setAttribute("aria-label", message("context.label"));
173
+ anchor.id = `rte-context-anchor-${++counter}`;
174
+ anchor.focus = () => editor.focus();
175
+ root.append(anchor, popup);
176
+ const entries = new Map();
177
+ let menu;
178
+ let at = { x: 0, y: 0 };
179
+ const draw = () => {
180
+ menu?.destroy();
181
+ entries.clear();
182
+ const children = [];
183
+ let group;
184
+ for (const item of toolbar.items) {
185
+ if (item.kind !== "button" && item.kind !== "toggle" && item.kind !== "dialog")
186
+ continue;
187
+ const next = item.group ?? "";
188
+ if (group !== undefined && next !== group) {
189
+ const rule = part(document.createElement("div"), "menu-separator");
190
+ rule.setAttribute("role", "separator");
191
+ children.push(rule);
192
+ }
193
+ group = next;
194
+ entries.set(item.id, item);
195
+ const entry = part(document.createElement("button"), "menu-item");
196
+ entry.type = "button";
197
+ entry.setAttribute("data-rte-menuitem", "");
198
+ entry.setAttribute("data-rte-menu-item", item.id);
199
+ if (item.kind === "toggle")
200
+ entry.setAttribute("role", "menuitemcheckbox");
201
+ entry.append(part(document.createElement("span"), "label"), part(document.createElement("span"), "menu-keys"));
202
+ children.push(entry);
203
+ }
204
+ popup.replaceChildren(...children);
205
+ menu = createMenu({
206
+ trigger: anchor, menu: popup,
207
+ onOpen: () => {
208
+ popup.style.position = "fixed";
209
+ popup.style.insetBlockStart = popup.style.insetInlineStart = "auto";
210
+ const w = popup.offsetWidth;
211
+ const h = popup.offsetHeight;
212
+ const view = root.ownerDocument.documentElement;
213
+ popup.style.left = `${Math.max(4, Math.min(at.x, view.clientWidth - w - 4))}px`;
214
+ popup.style.top = `${Math.max(4, Math.min(at.y, view.clientHeight - h - 4))}px`;
215
+ },
216
+ onSelect: (entry) => {
217
+ const id = entry.getAttribute("data-rte-menu-item") ?? "";
218
+ const item = entries.get(id);
219
+ if (item?.kind === "dialog") {
220
+ options.openDialog(item, returnTo);
221
+ return;
222
+ }
223
+ toolbar.activate(id);
224
+ editor.focus();
225
+ },
226
+ });
227
+ words();
228
+ };
229
+ const words = () => {
230
+ anchor.setAttribute("aria-label", message("context.label"));
231
+ for (const entry of popup.querySelectorAll("[data-rte-menu-item]")) {
232
+ const id = entry.getAttribute("data-rte-menu-item") ?? "";
233
+ const item = entries.get(id);
234
+ const state = toolbar.state(id);
235
+ if (!item || !state)
236
+ continue;
237
+ const label = message(`item.${id}`, state.label);
238
+ const name = entry.querySelector('[data-rte-part="label"]');
239
+ if (name)
240
+ name.textContent = label;
241
+ const keys = entry.querySelector('[data-rte-part="menu-keys"]');
242
+ if (keys)
243
+ keys.textContent = state.shortcutLabel ?? "";
244
+ entry.setAttribute("aria-disabled", String(state.disabled));
245
+ if (item.kind === "toggle")
246
+ entry.setAttribute("aria-checked", String(state.pressed === true));
247
+ }
248
+ };
249
+ draw();
250
+ const stopState = toolbar.subscribe(() => {
251
+ const drawn = [...popup.querySelectorAll("[data-rte-menu-item]")].map((el) => el.getAttribute("data-rte-menu-item")).join("\u0000");
252
+ if (drawn !== toolbar.items.filter((i) => i.kind === "button" || i.kind === "toggle" || i.kind === "dialog").map((i) => i.id).join("\u0000"))
253
+ draw();
254
+ else
255
+ words();
256
+ });
257
+ const onContextMenu = (e) => {
258
+ if ((e.shiftKey && e.button === 2) || e.pointerType === "touch")
259
+ return;
260
+ if (toolbar.items.length === 0)
261
+ return;
262
+ e.preventDefault();
263
+ let x = e.clientX;
264
+ let y = e.clientY;
265
+ if (x === 0 && y === 0) {
266
+ const selection = root.ownerDocument.getSelection();
267
+ const rect = selection && selection.rangeCount > 0 ? selection.getRangeAt(0).getBoundingClientRect() : editor.element.getBoundingClientRect();
268
+ x = rect.left;
269
+ y = rect.bottom;
270
+ }
271
+ at = { x, y };
272
+ words();
273
+ menu?.close(false);
274
+ menu?.open();
275
+ };
276
+ editor.element.addEventListener("contextmenu", onContextMenu);
277
+ redraws.push(draw);
278
+ stops.push(() => { editor.element.removeEventListener("contextmenu", onContextMenu); stopState(); menu?.destroy(); toolbar.destroy(); anchor.remove(); popup.remove(); });
279
+ }
280
+ return {
281
+ refresh: () => { for (const redraw of redraws)
282
+ redraw(); },
283
+ destroy: () => { for (const stop of stops.splice(0).reverse())
284
+ stop(); },
285
+ };
286
+ }
package/dist/messages.js CHANGED
@@ -1,5 +1,7 @@
1
1
  export const DEFAULT_MESSAGES = {
2
2
  "toolbar.label": "Formatting",
3
+ "floating.label": "Selection formatting",
4
+ "context.label": "Editing commands",
3
5
  "dialog.ok": "OK",
4
6
  "dialog.cancel": "Cancel",
5
7
  "field.required": "{label} is required.",
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 = `rk-shortcuts-title-${++counter}`;
6
+ const titleId = `rte-shortcuts-title-${++counter}`;
7
7
  element.setAttribute("aria-labelledby", titleId);
8
- element.setAttribute("data-rk-shortcuts", "");
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-rk-item="source"]');
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-rk-source");
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-rk-busy");
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-rk-source", "");
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-rk-item]");
38
- if (!control || control.getAttribute("data-rk-item") === "source")
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-rk-source");
64
+ content.removeAttribute("data-rte-source");
65
65
  },
66
66
  };
67
67
  }
@@ -1,5 +1,2 @@
1
- import { createUi } from "./ui.ts";
2
1
  import type { Ui, UiEditor, UiOptions } from "./types.ts";
3
2
  export declare function createStandardUi(editor: UiEditor, options: UiOptions): Ui;
4
- export { createUi };
5
- export type { Ui, UiEditor, UiOptions };
package/dist/standard.js CHANGED
@@ -3,4 +3,3 @@ import { createUi } from "./ui.js";
3
3
  export function createStandardUi(editor, options) {
4
4
  return createUi(editor, { ...options, defaults: { ...options.defaults, icons: { ...defaultIcons, ...options.defaults?.icons } } });
5
5
  }
6
- export { createUi };
package/dist/types.d.ts CHANGED
@@ -1,10 +1,10 @@
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-rk-part`). */
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" | "findbar" | "find-line" | "find-row" | "find-label" | "find-field" | "find-count" | "dialog" | "dialog-title" | "field-label" | "field-check" | "field" | "field-error" | "actions" | "action";
4
+ /** The parts the interface is made of. Selectors and `classes` address them by these names (as `data-rte-part`). */
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
- export type MessageKey = "toolbar.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"
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"
8
8
  /** Rename any item: `item.bold`, `item.link`, and so on. */
9
9
  | `item.${string}`;
10
10
  /** What a replacement part gets: the same headless state the default part uses, and a way to run the item. */
@@ -52,13 +52,24 @@ 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 `rk-root` class. */
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. */
59
59
  readonly status?: boolean;
60
60
  /** Which set of interface items to draw. Default: the main toolbar. */
61
61
  readonly slot?: string;
62
+ /**
63
+ * A short row of formatting controls over selected text (the items of the `toolbar.floating` slot; when the slot is empty it gets the
64
+ * common formatting controls). Default: off. It stays out of the way of touch selection, and Alt+F10 moves the focus into it.
65
+ */
66
+ readonly selectionToolbar?: boolean;
67
+ /**
68
+ * A menu of commands at the pointer on a right click (the items of the `menu.context` slot; when empty it gets undo, redo and the common
69
+ * formatting controls). Default: off. It replaces the browser's menu, spelling suggestions included, so Shift + right click still opens the
70
+ * browser's own, and a touch long-press is left to the browser.
71
+ */
72
+ readonly contextMenu?: boolean;
62
73
  /**
63
74
  * Style that every `setStyle` is layered on top of: icons and messages given here stay unless the new style gives the same name.
64
75
  * This is how the standard interface keeps its default icons when the theme is switched.