@rathkit/rte-ui 0.1.0-alpha.3 → 0.1.0-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/css/base.css +198 -190
- package/dist/element-define.d.ts +1 -0
- package/dist/element-define.js +2 -0
- package/dist/element.d.ts +23 -0
- package/dist/element.js +62 -0
- package/dist/findbar.js +10 -10
- package/dist/floating.d.ts +24 -0
- package/dist/floating.js +286 -0
- package/dist/messages.js +2 -0
- 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 +15 -4
- package/dist/ui.js +43 -31
- package/package.json +20 -6
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
|
};
|
|
@@ -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 {};
|
package/dist/floating.js
ADDED
|
@@ -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
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,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-
|
|
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 `
|
|
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.
|