@tensor_1/react 2.17.0 → 2.19.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.
Files changed (60) hide show
  1. package/README.md +131 -1
  2. package/dist/components/Comments.d.ts +18 -2
  3. package/dist/components/Comments.js +37 -12
  4. package/dist/components/EditorIcons.d.ts +39 -0
  5. package/dist/components/EditorIcons.js +51 -0
  6. package/dist/components/EmojiGlyph.d.ts +10 -0
  7. package/dist/components/EmojiGlyph.js +18 -0
  8. package/dist/components/EmojiPicker.d.ts +60 -0
  9. package/dist/components/EmojiPicker.js +277 -0
  10. package/dist/components/Markdown.d.ts +36 -0
  11. package/dist/components/Markdown.js +104 -0
  12. package/dist/components/ReactionBar.d.ts +57 -0
  13. package/dist/components/ReactionBar.js +81 -0
  14. package/dist/components/RelationshipGraph.d.ts +81 -3
  15. package/dist/components/RelationshipGraph.js +102 -59
  16. package/dist/components/RelationshipGraphLayered.d.ts +8 -0
  17. package/dist/components/RelationshipGraphLayered.js +420 -0
  18. package/dist/components/RelationshipGraphList.d.ts +16 -0
  19. package/dist/components/RelationshipGraphList.js +33 -0
  20. package/dist/components/RichTextEditor.d.ts +128 -0
  21. package/dist/components/RichTextEditor.js +954 -0
  22. package/dist/components/SaveStatus.d.ts +32 -0
  23. package/dist/components/SaveStatus.js +21 -0
  24. package/dist/components/Tabs.d.ts +10 -3
  25. package/dist/components/Tabs.js +5 -3
  26. package/dist/emoji-image-WQLh8ZQP.d.cts +128 -0
  27. package/dist/emoji-image.d.ts +25 -0
  28. package/dist/emoji-image.js +80 -0
  29. package/dist/emoji-model.d.ts +66 -0
  30. package/dist/emoji-model.js +108 -0
  31. package/dist/emoji.cjs +871 -0
  32. package/dist/emoji.d.cts +57 -0
  33. package/dist/emoji.d.ts +6 -0
  34. package/dist/emoji.js +18 -0
  35. package/dist/index.cjs +1503 -540
  36. package/dist/index.d.cts +245 -65
  37. package/dist/index.d.ts +3 -2
  38. package/dist/index.js +11 -2
  39. package/dist/markdown-model.d.ts +75 -0
  40. package/dist/markdown-model.js +438 -0
  41. package/dist/markdown.cjs +585 -0
  42. package/dist/markdown.d.cts +115 -0
  43. package/dist/markdown.d.ts +4 -0
  44. package/dist/markdown.js +9 -0
  45. package/dist/overlay-position-CEGSz6Z2.d.cts +59 -0
  46. package/dist/relationship-graph-model.d.ts +112 -1
  47. package/dist/relationship-graph-model.js +420 -1
  48. package/dist/rich-text/emoji-items.d.ts +14 -0
  49. package/dist/rich-text/emoji-items.js +7 -0
  50. package/dist/rich-text/pipeline.d.ts +91 -0
  51. package/dist/rich-text/pipeline.js +379 -0
  52. package/dist/rich-text/round-trip.d.ts +24 -0
  53. package/dist/rich-text/round-trip.js +11 -0
  54. package/dist/rich-text.cjs +2578 -0
  55. package/dist/rich-text.d.cts +148 -0
  56. package/dist/rich-text.d.ts +8 -0
  57. package/dist/rich-text.js +10 -0
  58. package/dist/url-policy.d.ts +9 -0
  59. package/dist/url-policy.js +11 -0
  60. package/package.json +82 -1
@@ -0,0 +1,954 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
4
+ import { Extension, InputRule, mergeAttributes } from "@tiptap/core";
5
+ import { EditorContent, useEditor, useEditorState } from "@tiptap/react";
6
+ import { Markdown as MarkdownExtension } from "@tiptap/markdown";
7
+ import { Placeholder } from "@tiptap/extensions";
8
+ import { Image } from "@tiptap/extension-image";
9
+ import { Emoji } from "@tiptap/extension-emoji";
10
+ import { Suggestion } from "@tiptap/suggestion";
11
+ import { Plugin, PluginKey } from "@tiptap/pm/state";
12
+ import { Decoration, DecorationSet } from "@tiptap/pm/view";
13
+ import { cx } from "../cx.js";
14
+ import { computePosition } from "../overlay-position.js";
15
+ import { formatLabel } from "../emoji-model.js";
16
+ import { canRenderEmoji, emojiImageSrc, emojiPattern, normalizeEmojiImageBaseUrl } from "../emoji-image.js";
17
+ import { OFFLINE_EMOJI_ITEMS } from "../rich-text/emoji-items.js";
18
+ import { useEmojiRenderer } from "./EmojiGlyph.js";
19
+ import {
20
+ createMarked,
21
+ createSchemaExtensions,
22
+ defaultIsSafeUrl,
23
+ patchManager,
24
+ readMarkdown,
25
+ writeMarkdown
26
+ } from "../rich-text/pipeline.js";
27
+ import { Composer, ComposerBar } from "./Composer.js";
28
+ import { EditorIcon } from "./EditorIcons.js";
29
+ import { EmojiPicker } from "./EmojiPicker.js";
30
+ import { Markdown } from "./Markdown.js";
31
+ import { Popover } from "./Popover.js";
32
+ import { Textarea } from "./Textarea.js";
33
+ const DEFAULT_RICH_TEXT_LABELS = {
34
+ toolbar: "Formatting",
35
+ heading1: "Heading 1",
36
+ heading2: "Heading 2",
37
+ heading3: "Heading 3",
38
+ bold: "Bold",
39
+ italic: "Italic",
40
+ strike: "Strikethrough",
41
+ code: "Inline code",
42
+ bulletList: "Bulleted list",
43
+ orderedList: "Numbered list",
44
+ taskList: "Checklist",
45
+ blockquote: "Quote",
46
+ codeBlock: "Code block",
47
+ horizontalRule: "Divider",
48
+ link: "Link",
49
+ linkUrl: "Link address",
50
+ linkApply: "Apply",
51
+ linkRemove: "Remove link",
52
+ linkInvalid: "Use a web, mail or in-app address.",
53
+ table: "Table",
54
+ tableAddRow: "Add row",
55
+ tableAddColumn: "Add column",
56
+ tableDeleteRow: "Delete row",
57
+ tableDeleteColumn: "Delete column",
58
+ tableDelete: "Delete table",
59
+ image: "Image",
60
+ imageUploading: "Uploading image\u2026",
61
+ imageFailed: "The image could not be added.",
62
+ emoji: "Emoji",
63
+ emojiSuggestions: "Emoji suggestions",
64
+ undo: "Undo",
65
+ redo: "Redo",
66
+ sourceMode: "Show markdown",
67
+ richMode: "Show formatted",
68
+ characterCount: "{count} of {max} characters"
69
+ };
70
+ const COMMON = ["bold", "italic", "strike", "code", "bulletList", "orderedList", "taskList", "blockquote", "codeBlock", "link", "emoji", "history", "source"];
71
+ const PRESETS = {
72
+ full: /* @__PURE__ */ new Set([...COMMON, "heading", "horizontalRule", "table", "image"]),
73
+ comment: new Set(COMMON),
74
+ template: /* @__PURE__ */ new Set([...COMMON, "heading", "horizontalRule", "table"])
75
+ };
76
+ const emojiSuggestionKey = new PluginKey("uixEmojiSuggestion");
77
+ const SHORTCODE_RE = /:([a-zA-Z0-9_+-]+):$/;
78
+ const emojiItems = OFFLINE_EMOJI_ITEMS;
79
+ const findShortcode = (name) => emojiItems.find((item) => item.emoji && (item.name === name || item.shortcodes.includes(name)));
80
+ function suggestEmoji(query, limit = 8) {
81
+ const q = query.toLowerCase();
82
+ if (q.length < 2) return [];
83
+ const starts = [];
84
+ const tagged = [];
85
+ for (const item of emojiItems) {
86
+ if (!item.emoji) continue;
87
+ if (item.shortcodes.some((s) => s.startsWith(q))) starts.push(item);
88
+ else if (item.tags.some((t) => t.startsWith(q))) tagged.push(item);
89
+ if (starts.length >= limit) break;
90
+ }
91
+ return [...starts, ...tagged].slice(0, limit);
92
+ }
93
+ const htmlAttr = (value) => value === void 0 || value === false || value === "" ? void 0 : String(value);
94
+ const popoverOpen = (el) => {
95
+ try {
96
+ return el.matches(":popover-open");
97
+ } catch {
98
+ return false;
99
+ }
100
+ };
101
+ const isMac = () => typeof navigator !== "undefined" && /mac|iphone|ipad/i.test(navigator.platform || navigator.userAgent);
102
+ function RichTextEditorFallback(props) {
103
+ const {
104
+ value,
105
+ onChange,
106
+ onSubmitShortcut,
107
+ placeholder,
108
+ disabled,
109
+ readOnly,
110
+ maxLength,
111
+ id,
112
+ name,
113
+ variant = "field",
114
+ toolbarEnd,
115
+ minRows,
116
+ className,
117
+ onBlur,
118
+ labels: labelOverrides
119
+ } = props;
120
+ const labels = { ...DEFAULT_RICH_TEXT_LABELS, ...labelOverrides };
121
+ const counterId = useId();
122
+ const over = maxLength !== void 0 && value.length > maxLength;
123
+ const counter = maxLength !== void 0 ? /* @__PURE__ */ jsx("span", { id: counterId, className: "uix-rich-text__counter", "data-over": over || void 0, children: formatLabel(labels.characterCount, { count: value.length, max: maxLength }) }) : null;
124
+ const textarea = /* @__PURE__ */ jsx(
125
+ Textarea,
126
+ {
127
+ id,
128
+ name,
129
+ value,
130
+ onChange: (event) => onChange(event.target.value),
131
+ onBlur,
132
+ onKeyDown: (event) => {
133
+ if (onSubmitShortcut && (event.metaKey || event.ctrlKey) && event.key === "Enter") {
134
+ event.preventDefault();
135
+ onSubmitShortcut();
136
+ }
137
+ },
138
+ placeholder,
139
+ disabled,
140
+ readOnly,
141
+ rows: minRows ?? (variant === "composer" ? 3 : 4),
142
+ className: "uix-rich-text__source",
143
+ "aria-label": props["aria-label"],
144
+ "aria-labelledby": props["aria-labelledby"],
145
+ "aria-describedby": [props["aria-describedby"], counter ? counterId : void 0].filter(Boolean).join(" ") || void 0,
146
+ "aria-invalid": props["aria-invalid"] || over || void 0,
147
+ "aria-required": props["aria-required"] || void 0,
148
+ "data-editor-fallback": ""
149
+ }
150
+ );
151
+ if (variant === "composer") {
152
+ return /* @__PURE__ */ jsxs(Composer, { className: cx("uix-rich-text", "uix-rich-text--composer", className), children: [
153
+ textarea,
154
+ counter || toolbarEnd ? /* @__PURE__ */ jsxs(ComposerBar, { className: "uix-rich-text__bar", children: [
155
+ counter,
156
+ toolbarEnd
157
+ ] }) : null
158
+ ] });
159
+ }
160
+ return /* @__PURE__ */ jsxs("div", { className: cx("uix-rich-text", "uix-rich-text--fallback", className), "data-disabled": disabled || void 0, children: [
161
+ textarea,
162
+ counter ? /* @__PURE__ */ jsx("div", { className: "uix-rich-text__footer", children: counter }) : null
163
+ ] });
164
+ }
165
+ function RichTextEditor(props) {
166
+ const {
167
+ value,
168
+ onChange,
169
+ features = "full",
170
+ headingLevels = [1, 2, 3],
171
+ onUploadImage,
172
+ isSafeUrl = defaultIsSafeUrl,
173
+ resolveImageSrc,
174
+ onSubmitShortcut,
175
+ emoji = true,
176
+ maxLength,
177
+ placeholder,
178
+ disabled = false,
179
+ readOnly = false,
180
+ labels: labelOverrides,
181
+ emojiPickerLabels,
182
+ emojiLocale,
183
+ id,
184
+ name,
185
+ variant = "field",
186
+ toolbarEnd,
187
+ minRows,
188
+ className,
189
+ onBlur,
190
+ emojiImageBaseUrl
191
+ } = props;
192
+ const emojiBase = useMemo(() => normalizeEmojiImageBaseUrl(emojiImageBaseUrl), [emojiImageBaseUrl]);
193
+ const renderEmoji = useEmojiRenderer(emojiImageBaseUrl);
194
+ const labels = useMemo(() => ({ ...DEFAULT_RICH_TEXT_LABELS, ...labelOverrides }), [labelOverrides]);
195
+ const uid = useId();
196
+ const counterId = `${uid}-counter`;
197
+ const statusId = `${uid}-status`;
198
+ const suggestId = `${uid}-emoji`;
199
+ const linkPopoverId = `${uid}-link`;
200
+ const controls = PRESETS[features];
201
+ const has = (control) => controls.has(control) && (control !== "emoji" || emoji) && (control !== "image" || !!onUploadImage);
202
+ const latest = useRef({ onChange, onSubmitShortcut, onBlur, isSafeUrl, resolveImageSrc, onUploadImage, placeholder, imagesOn: false });
203
+ latest.current = {
204
+ onChange,
205
+ onSubmitShortcut,
206
+ onBlur,
207
+ isSafeUrl,
208
+ resolveImageSrc,
209
+ onUploadImage,
210
+ placeholder,
211
+ imagesOn: controls.has("image") && !!onUploadImage
212
+ };
213
+ const lastValue = useRef(value);
214
+ const origin = useRef(null);
215
+ const [markdown, setMarkdown] = useState(value);
216
+ const [mode, setMode] = useState("rich");
217
+ const [status, setStatus] = useState(null);
218
+ const [suggest, setSuggest] = useState(null);
219
+ const [suggestIndex, setSuggestIndex] = useState(0);
220
+ const [linkUrl, setLinkUrl] = useState("");
221
+ const [linkError, setLinkError] = useState(false);
222
+ const [rovingIndex, setRovingIndex] = useState(0);
223
+ const toolbarRef = useRef(null);
224
+ const linkButtonRef = useRef(null);
225
+ const linkInputRef = useRef(null);
226
+ const fileRef = useRef(null);
227
+ const sourceRef = useRef(null);
228
+ const openLinkRef = useRef(() => {
229
+ });
230
+ const suggestState = useRef({ suggest, suggestIndex });
231
+ suggestState.current = { suggest, suggestIndex };
232
+ const bridge = useRef({ update: () => {
233
+ }, keyDown: () => false });
234
+ bridge.current = {
235
+ update: (p) => {
236
+ if (!p || p.items.length === 0) {
237
+ setSuggest(null);
238
+ return;
239
+ }
240
+ setSuggest({ query: p.query, items: p.items, command: p.command, rect: p.clientRect?.() ?? null });
241
+ setSuggestIndex(0);
242
+ },
243
+ keyDown: ({ event }) => {
244
+ const { suggest: open, suggestIndex: index } = suggestState.current;
245
+ if (!open) return false;
246
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
247
+ const delta = event.key === "ArrowDown" ? 1 : -1;
248
+ setSuggestIndex((index + delta + open.items.length) % open.items.length);
249
+ return true;
250
+ }
251
+ if (event.key === "Enter" || event.key === "Tab") {
252
+ const item = open.items[index];
253
+ if (item) open.command(item);
254
+ return true;
255
+ }
256
+ if (event.key === "Escape") {
257
+ setSuggest(null);
258
+ return true;
259
+ }
260
+ return false;
261
+ }
262
+ };
263
+ const headingKey = [...headingLevels].sort().join(",");
264
+ const extensions = useMemo(() => {
265
+ const image = Image.extend({
266
+ parseHTML() {
267
+ return [{
268
+ tag: "img[src]",
269
+ // Pasted images are kept only when the consumer allows images and can resolve them.
270
+ getAttrs: (el) => {
271
+ const src = el.getAttribute("src") ?? "";
272
+ return latest.current.imagesOn && latest.current.resolveImageSrc?.(src) ? null : false;
273
+ }
274
+ }];
275
+ },
276
+ renderHTML({ node, HTMLAttributes }) {
277
+ const src = String(node.attrs.src ?? "");
278
+ const resolved = latest.current.resolveImageSrc?.(src) ?? null;
279
+ if (resolved) return ["img", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { src: resolved })];
280
+ return ["span", { class: "uix-rich-text__image-link", "data-src": src, title: src }, node.attrs.alt || src];
281
+ }
282
+ }).configure({ inline: true, allowBase64: false });
283
+ const unicodeEmoji = Emoji.extend({
284
+ addStorage() {
285
+ return { emojis: this.options.emojis, isSupported: () => true };
286
+ },
287
+ // Emoji are inserted as text; the node type exists only because the extension defines it.
288
+ // Unknown names (e.g. pasted from another editor) keep their shortcode rather than vanish.
289
+ renderMarkdown: (node) => {
290
+ const name2 = String(node.attrs?.name ?? "");
291
+ return findShortcode(name2)?.emoji ?? (name2 ? `:${name2}:` : "");
292
+ },
293
+ addInputRules() {
294
+ return [new InputRule({
295
+ find: SHORTCODE_RE,
296
+ handler: ({ range, match, chain }) => {
297
+ const item = findShortcode(match[1] ?? "");
298
+ if (item?.emoji) chain().insertContentAt(range, item.emoji).run();
299
+ }
300
+ })];
301
+ },
302
+ addPasteRules() {
303
+ return [];
304
+ },
305
+ addProseMirrorPlugins() {
306
+ return [Suggestion({ editor: this.editor, ...this.options.suggestion })];
307
+ }
308
+ }).configure({
309
+ // Offline list: no CDN fallbackImage, no GitHub image-only emoji (on-prem, CSP img-src 'self').
310
+ emojis: [...emojiItems],
311
+ enableEmoticons: false,
312
+ forceFallbackImages: false,
313
+ suggestion: {
314
+ char: ":",
315
+ pluginKey: emojiSuggestionKey,
316
+ items: ({ query }) => suggestEmoji(query),
317
+ command: ({ editor: editor2, range, props: item }) => {
318
+ if (item.emoji) editor2.chain().focus().insertContentAt(range, item.emoji).run();
319
+ },
320
+ allow: ({ state: state2, range }) => !state2.doc.resolve(range.from).parent.type.spec.code,
321
+ render: () => ({
322
+ onStart: (p) => bridge.current.update(p),
323
+ onUpdate: (p) => bridge.current.update(p),
324
+ onExit: () => bridge.current.update(null),
325
+ onKeyDown: (p) => bridge.current.keyDown(p)
326
+ })
327
+ }
328
+ });
329
+ const shortcuts = Extension.create({
330
+ name: "uixRichTextShortcuts",
331
+ addKeyboardShortcuts() {
332
+ return {
333
+ "Mod-Enter": () => {
334
+ const submit = latest.current.onSubmitShortcut;
335
+ if (!submit) return false;
336
+ submit();
337
+ return true;
338
+ },
339
+ "Mod-k": () => {
340
+ openLinkRef.current();
341
+ return true;
342
+ },
343
+ "Mod-Shift-x": () => this.editor.commands.toggleStrike()
344
+ };
345
+ }
346
+ });
347
+ const emojiImages = Extension.create({
348
+ name: "uixEmojiImages",
349
+ addProseMirrorPlugins() {
350
+ const key = new PluginKey("uixEmojiImages");
351
+ const build = (doc) => {
352
+ if (!emojiBase) return DecorationSet.empty;
353
+ const decorations = [];
354
+ doc.descendants((node, pos) => {
355
+ if (node.type.spec.code) return false;
356
+ if (!node.isText || !node.text) return true;
357
+ for (const match of node.text.matchAll(emojiPattern())) {
358
+ const glyph = match[0];
359
+ if (canRenderEmoji(glyph)) continue;
360
+ const from = pos + (match.index ?? 0);
361
+ decorations.push(Decoration.inline(from, from + glyph.length, { class: "uix-rich-text__emoji-text" }));
362
+ decorations.push(Decoration.widget(from, () => {
363
+ const img = document.createElement("img");
364
+ img.className = "uix-emoji-img";
365
+ img.alt = glyph;
366
+ img.draggable = false;
367
+ img.src = emojiImageSrc(emojiBase, glyph);
368
+ return img;
369
+ }, { side: -1, key: `emoji-${glyph}`, ignoreSelection: true }));
370
+ }
371
+ return false;
372
+ });
373
+ return DecorationSet.create(doc, decorations);
374
+ };
375
+ return [new Plugin({
376
+ key,
377
+ state: {
378
+ init: (_, state2) => build(state2.doc),
379
+ apply: (tr, previous) => tr.docChanged ? build(tr.doc) : previous
380
+ },
381
+ props: { decorations: (state2) => key.getState(state2) }
382
+ })];
383
+ }
384
+ });
385
+ return [
386
+ ...createSchemaExtensions({
387
+ headingLevels: headingKey.split(",").map(Number),
388
+ isSafeUrl: (url) => latest.current.isSafeUrl(url),
389
+ image
390
+ }),
391
+ MarkdownExtension.configure({ marked: createMarked() }),
392
+ Placeholder.configure({ placeholder: () => latest.current.placeholder ?? "" }),
393
+ shortcuts,
394
+ ...emoji ? [unicodeEmoji] : [],
395
+ ...emojiBase ? [emojiImages] : []
396
+ ];
397
+ }, [headingKey, emoji, emojiBase]);
398
+ const managerOf = (ed) => {
399
+ const manager = ed.markdown ?? ed.storage.markdown.manager;
400
+ return patchManager(manager);
401
+ };
402
+ const load = useCallback((ed, source) => {
403
+ const read = readMarkdown(source, managerOf(ed), ed.schema);
404
+ origin.current = read;
405
+ ed.chain().setMeta("addToHistory", false).setContent(read.doc, { emitUpdate: false }).run();
406
+ }, []);
407
+ const emit = (next) => {
408
+ if (next === lastValue.current) return;
409
+ lastValue.current = next;
410
+ setMarkdown(next);
411
+ latest.current.onChange(next);
412
+ };
413
+ const uploadImage = async (ed, file) => {
414
+ const upload = latest.current.onUploadImage;
415
+ if (!upload || !file.type.startsWith("image/")) return;
416
+ setStatus({ kind: "busy", text: labels.imageUploading });
417
+ try {
418
+ const { src, alt } = await upload(file);
419
+ ed.chain().focus().setImage({ src, alt }).run();
420
+ setStatus(null);
421
+ } catch (error) {
422
+ const message = error instanceof Error && error.message ? error.message : "";
423
+ setStatus({ kind: "error", text: message ? `${labels.imageFailed} ${message}` : labels.imageFailed });
424
+ }
425
+ };
426
+ const editor = useEditor({
427
+ extensions,
428
+ immediatelyRender: false,
429
+ // Styles ship in @tensor_1/tokens (rich-text.css); no runtime <style> injection under CSP.
430
+ injectCSS: false,
431
+ shouldRerenderOnTransaction: false,
432
+ editable: !disabled && !readOnly,
433
+ editorProps: {
434
+ handlePaste: (_view, event) => {
435
+ const files = Array.from(event.clipboardData?.files ?? []).filter((f) => f.type.startsWith("image/"));
436
+ if (!files.length || !latest.current.imagesOn || !editorRef.current) return false;
437
+ files.forEach((file) => void uploadImage(editorRef.current, file));
438
+ return true;
439
+ },
440
+ handleDrop: (_view, event) => {
441
+ const files = Array.from(event.dataTransfer?.files ?? []).filter((f) => f.type.startsWith("image/"));
442
+ if (!files.length || !latest.current.imagesOn || !editorRef.current) return false;
443
+ event.preventDefault();
444
+ files.forEach((file) => void uploadImage(editorRef.current, file));
445
+ return true;
446
+ }
447
+ },
448
+ onCreate: ({ editor: ed }) => load(ed, lastValue.current),
449
+ onUpdate: ({ editor: ed }) => {
450
+ if (!origin.current) return;
451
+ emit(writeMarkdown(ed.getJSON(), origin.current, managerOf(ed)));
452
+ },
453
+ onBlur: () => latest.current.onBlur?.()
454
+ }, [extensions]);
455
+ const editorRef = useRef(null);
456
+ editorRef.current = editor;
457
+ useEffect(() => {
458
+ if (value === lastValue.current) return;
459
+ lastValue.current = value;
460
+ setMarkdown(value);
461
+ if (editor && !editor.isDestroyed) load(editor, value);
462
+ }, [value, editor, load]);
463
+ useEffect(() => {
464
+ if (editor && !editor.isDestroyed) editor.setEditable(!disabled && !readOnly, false);
465
+ }, [editor, disabled, readOnly]);
466
+ const over = maxLength !== void 0 && markdown.length > maxLength;
467
+ const invalid = props["aria-invalid"] || over;
468
+ const describedBy = [props["aria-describedby"], maxLength !== void 0 ? counterId : void 0, status ? statusId : void 0].filter(Boolean).join(" ") || void 0;
469
+ const suggestOpen = !!suggest && mode === "rich";
470
+ const rows = minRows ?? (variant === "composer" ? 3 : 4);
471
+ useEffect(() => {
472
+ if (!editor || editor.isDestroyed) return;
473
+ editor.setOptions({
474
+ editorProps: {
475
+ ...editor.options.editorProps,
476
+ attributes: Object.fromEntries(Object.entries({
477
+ id: mode === "rich" ? id : void 0,
478
+ role: "textbox",
479
+ "aria-multiline": "true",
480
+ "aria-label": props["aria-label"],
481
+ "aria-labelledby": props["aria-labelledby"],
482
+ "aria-describedby": describedBy,
483
+ "aria-invalid": htmlAttr(invalid),
484
+ "aria-required": htmlAttr(props["aria-required"]),
485
+ "aria-disabled": htmlAttr(disabled),
486
+ "aria-readonly": htmlAttr(readOnly),
487
+ "aria-autocomplete": emoji ? "list" : void 0,
488
+ "aria-controls": suggestOpen ? suggestId : void 0,
489
+ "aria-activedescendant": suggestOpen ? `${suggestId}-${suggestIndex}` : void 0,
490
+ class: "uix-rich-text__content uix-prose",
491
+ style: `min-height: calc(${rows} * 1lh + 2 * var(--uix-space-3))`
492
+ }).filter(([, v]) => v !== void 0))
493
+ }
494
+ });
495
+ }, [
496
+ editor,
497
+ mode,
498
+ id,
499
+ props["aria-label"],
500
+ props["aria-labelledby"],
501
+ describedBy,
502
+ invalid,
503
+ props["aria-required"],
504
+ disabled,
505
+ readOnly,
506
+ emoji,
507
+ suggestOpen,
508
+ suggestId,
509
+ suggestIndex,
510
+ rows
511
+ ]);
512
+ const state = useEditorState({
513
+ editor,
514
+ selector: ({ editor: ed }) => {
515
+ if (!ed) return null;
516
+ const can = ed.can();
517
+ return {
518
+ bold: ed.isActive("bold"),
519
+ italic: ed.isActive("italic"),
520
+ strike: ed.isActive("strike"),
521
+ code: ed.isActive("code"),
522
+ h1: ed.isActive("heading", { level: 1 }),
523
+ h2: ed.isActive("heading", { level: 2 }),
524
+ h3: ed.isActive("heading", { level: 3 }),
525
+ bulletList: ed.isActive("bulletList"),
526
+ orderedList: ed.isActive("orderedList"),
527
+ taskList: ed.isActive("taskList"),
528
+ blockquote: ed.isActive("blockquote"),
529
+ codeBlock: ed.isActive("codeBlock"),
530
+ link: ed.isActive("link"),
531
+ inTable: ed.isActive("table"),
532
+ canUndo: can.undo(),
533
+ canRedo: can.redo()
534
+ };
535
+ }
536
+ });
537
+ const openLink = () => {
538
+ if (!editor || mode !== "rich") return;
539
+ setLinkUrl(String(editor.getAttributes("link").href ?? ""));
540
+ setLinkError(false);
541
+ const pop = document.getElementById(linkPopoverId);
542
+ if (pop && !popoverOpen(pop)) pop.showPopover();
543
+ requestAnimationFrame(() => linkInputRef.current?.select());
544
+ };
545
+ openLinkRef.current = openLink;
546
+ const closeLink = () => {
547
+ const pop = document.getElementById(linkPopoverId);
548
+ if (pop && popoverOpen(pop)) pop.hidePopover();
549
+ editor?.commands.focus();
550
+ };
551
+ const applyLink = () => {
552
+ if (!editor) return;
553
+ const url = linkUrl.trim();
554
+ if (!url) {
555
+ editor.chain().focus().extendMarkRange("link").unsetLink().run();
556
+ closeLink();
557
+ return;
558
+ }
559
+ if (!isSafeUrl(url)) {
560
+ setLinkError(true);
561
+ linkInputRef.current?.focus();
562
+ return;
563
+ }
564
+ const chain = editor.chain().focus().extendMarkRange("link");
565
+ if (editor.state.selection.empty && !editor.isActive("link")) {
566
+ chain.insertContent({ type: "text", text: url, marks: [{ type: "link", attrs: { href: url } }] }).run();
567
+ } else {
568
+ chain.setLink({ href: url }).run();
569
+ }
570
+ closeLink();
571
+ };
572
+ const toggleMode = () => {
573
+ if (!editor) return;
574
+ if (mode === "rich") {
575
+ setMode("source");
576
+ requestAnimationFrame(() => sourceRef.current?.focus());
577
+ } else {
578
+ load(editor, lastValue.current);
579
+ setMode("rich");
580
+ requestAnimationFrame(() => editor.commands.focus());
581
+ }
582
+ };
583
+ const insertEmoji = (glyph) => {
584
+ if (mode === "source") {
585
+ const box = sourceRef.current;
586
+ if (!box) return;
587
+ const next = `${box.value.slice(0, box.selectionStart)}${glyph}${box.value.slice(box.selectionEnd)}`;
588
+ const caret = box.selectionStart + glyph.length;
589
+ emit(next);
590
+ requestAnimationFrame(() => {
591
+ box.focus();
592
+ box.setSelectionRange(caret, caret);
593
+ });
594
+ return;
595
+ }
596
+ editor?.chain().focus().insertContent(glyph).run();
597
+ };
598
+ const run = (fn) => () => {
599
+ if (editor) fn(editor);
600
+ };
601
+ const locked = disabled || !editor;
602
+ const richOnly = locked || mode === "source";
603
+ const shortcut = (key) => `${isMac() ? "\u2318" : "Ctrl+"}${key}`;
604
+ const groups = [];
605
+ if (has("heading")) {
606
+ groups.push(headingLevels.map((level) => ({
607
+ key: `h${level}`,
608
+ icon: `heading${level}`,
609
+ label: labels[`heading${level}`],
610
+ pressed: !!state?.[`h${level}`],
611
+ disabled: richOnly,
612
+ onClick: run((ed) => ed.chain().focus().toggleHeading({ level }).run())
613
+ })));
614
+ }
615
+ const marks = [];
616
+ if (has("bold")) marks.push({ key: "bold", icon: "bold", label: labels.bold, hint: shortcut("B"), pressed: state?.bold, disabled: richOnly, onClick: run((ed) => ed.chain().focus().toggleBold().run()) });
617
+ if (has("italic")) marks.push({ key: "italic", icon: "italic", label: labels.italic, hint: shortcut("I"), pressed: state?.italic, disabled: richOnly, onClick: run((ed) => ed.chain().focus().toggleItalic().run()) });
618
+ if (has("strike")) marks.push({ key: "strike", icon: "strike", label: labels.strike, hint: shortcut("Shift+X"), pressed: state?.strike, disabled: richOnly, onClick: run((ed) => ed.chain().focus().toggleStrike().run()) });
619
+ if (has("code")) marks.push({ key: "code", icon: "code", label: labels.code, hint: shortcut("E"), pressed: state?.code, disabled: richOnly, onClick: run((ed) => ed.chain().focus().toggleCode().run()) });
620
+ if (has("link")) marks.push({ key: "link", icon: "link", label: labels.link, hint: shortcut("K"), pressed: state?.link, disabled: richOnly, onClick: openLink });
621
+ if (marks.length) groups.push(marks);
622
+ const blocks = [];
623
+ if (has("bulletList")) blocks.push({ key: "bulletList", icon: "bulletList", label: labels.bulletList, pressed: state?.bulletList, disabled: richOnly, onClick: run((ed) => ed.chain().focus().toggleBulletList().run()) });
624
+ if (has("orderedList")) blocks.push({ key: "orderedList", icon: "orderedList", label: labels.orderedList, pressed: state?.orderedList, disabled: richOnly, onClick: run((ed) => ed.chain().focus().toggleOrderedList().run()) });
625
+ if (has("taskList")) blocks.push({ key: "taskList", icon: "taskList", label: labels.taskList, pressed: state?.taskList, disabled: richOnly, onClick: run((ed) => ed.chain().focus().toggleTaskList().run()) });
626
+ if (has("blockquote")) blocks.push({ key: "blockquote", icon: "blockquote", label: labels.blockquote, pressed: state?.blockquote, disabled: richOnly, onClick: run((ed) => ed.chain().focus().toggleBlockquote().run()) });
627
+ if (has("codeBlock")) blocks.push({ key: "codeBlock", icon: "codeBlock", label: labels.codeBlock, pressed: state?.codeBlock, disabled: richOnly, onClick: run((ed) => ed.chain().focus().toggleCodeBlock().run()) });
628
+ if (has("horizontalRule")) blocks.push({ key: "horizontalRule", icon: "horizontalRule", label: labels.horizontalRule, disabled: richOnly, onClick: run((ed) => ed.chain().focus().setHorizontalRule().run()) });
629
+ if (blocks.length) groups.push(blocks);
630
+ const inserts = [];
631
+ if (has("table")) inserts.push({ key: "table", icon: "table", label: labels.table, pressed: state?.inTable, disabled: richOnly || state?.inTable, onClick: run((ed) => ed.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run()) });
632
+ if (has("image")) inserts.push({ key: "image", icon: "image", label: labels.image, disabled: richOnly || status?.kind === "busy", onClick: () => fileRef.current?.click() });
633
+ if (inserts.length) groups.push(inserts);
634
+ if (has("table") && state?.inTable && mode === "rich") {
635
+ groups.push([
636
+ { key: "tableAddRow", icon: "tableAddRow", label: labels.tableAddRow, disabled: locked, onClick: run((ed) => ed.chain().focus().addRowAfter().run()) },
637
+ { key: "tableAddColumn", icon: "tableAddColumn", label: labels.tableAddColumn, disabled: locked, onClick: run((ed) => ed.chain().focus().addColumnAfter().run()) },
638
+ { key: "tableDeleteRow", icon: "tableDeleteRow", label: labels.tableDeleteRow, disabled: locked, onClick: run((ed) => ed.chain().focus().deleteRow().run()) },
639
+ { key: "tableDeleteColumn", icon: "tableDeleteColumn", label: labels.tableDeleteColumn, disabled: locked, onClick: run((ed) => ed.chain().focus().deleteColumn().run()) },
640
+ { key: "tableDelete", icon: "trash", label: labels.tableDelete, disabled: locked, onClick: run((ed) => ed.chain().focus().deleteTable().run()) }
641
+ ]);
642
+ }
643
+ const trailing = [];
644
+ if (has("history")) {
645
+ trailing.push({ key: "undo", icon: "undo", label: labels.undo, hint: shortcut("Z"), disabled: richOnly || !state?.canUndo, onClick: run((ed) => ed.chain().focus().undo().run()) });
646
+ trailing.push({ key: "redo", icon: "redo", label: labels.redo, hint: shortcut(isMac() ? "Shift+Z" : "Y"), disabled: richOnly || !state?.canRedo, onClick: run((ed) => ed.chain().focus().redo().run()) });
647
+ }
648
+ if (has("source")) {
649
+ trailing.push({
650
+ key: "source",
651
+ icon: mode === "rich" ? "sourceMode" : "richMode",
652
+ label: mode === "rich" ? labels.sourceMode : labels.richMode,
653
+ pressed: mode === "source",
654
+ disabled: locked,
655
+ onClick: toggleMode
656
+ });
657
+ }
658
+ const tools = () => Array.from(toolbarRef.current?.querySelectorAll("button[data-tool]:not(:disabled)") ?? []);
659
+ const onToolbarKeyDown = (event) => {
660
+ const list = tools();
661
+ const current = list.indexOf(document.activeElement);
662
+ if (current < 0) return;
663
+ let next = current;
664
+ if (event.key === "ArrowRight") next = (current + 1) % list.length;
665
+ else if (event.key === "ArrowLeft") next = (current - 1 + list.length) % list.length;
666
+ else if (event.key === "Home") next = 0;
667
+ else if (event.key === "End") next = list.length - 1;
668
+ else return;
669
+ event.preventDefault();
670
+ list[next].focus();
671
+ list[next].scrollIntoView?.({ block: "nearest", inline: "nearest" });
672
+ };
673
+ const enabledCount = { value: 0 };
674
+ const renderTool = (tool) => {
675
+ const index = tool.disabled ? -1 : enabledCount.value++;
676
+ return /* @__PURE__ */ jsx(
677
+ "button",
678
+ {
679
+ ref: tool.key === "link" ? linkButtonRef : void 0,
680
+ type: "button",
681
+ "data-tool": tool.key,
682
+ className: "uix-rich-text__tool",
683
+ "aria-label": tool.label,
684
+ title: tool.hint ? `${tool.label} (${tool.hint})` : tool.label,
685
+ "aria-pressed": tool.pressed === void 0 ? void 0 : !!tool.pressed,
686
+ "aria-keyshortcuts": tool.hint ? tool.hint.replace("\u2318", "Meta+") : void 0,
687
+ disabled: tool.disabled,
688
+ tabIndex: index === rovingIndex ? 0 : -1,
689
+ onFocus: () => {
690
+ if (index >= 0) setRovingIndex(index);
691
+ },
692
+ onMouseDown: (event) => {
693
+ if (tool.key !== "source") event.preventDefault();
694
+ },
695
+ onClick: tool.onClick,
696
+ children: /* @__PURE__ */ jsx(EditorIcon, { name: tool.icon })
697
+ },
698
+ tool.key
699
+ );
700
+ };
701
+ useEffect(() => {
702
+ const count = tools().length;
703
+ if (count && rovingIndex >= count) setRovingIndex(0);
704
+ });
705
+ const toolbar = readOnly ? null : /* @__PURE__ */ jsxs(
706
+ "div",
707
+ {
708
+ ref: toolbarRef,
709
+ className: "uix-rich-text__toolbar",
710
+ role: "toolbar",
711
+ "aria-label": labels.toolbar,
712
+ "aria-controls": editor || mode === "source" ? id : void 0,
713
+ onKeyDown: onToolbarKeyDown,
714
+ children: [
715
+ groups.map((group, g) => /* @__PURE__ */ jsx("div", { className: "uix-rich-text__group", role: "group", children: group.map(renderTool) }, g)),
716
+ has("emoji") ? /* @__PURE__ */ jsx("div", { className: "uix-rich-text__group", role: "group", children: /* @__PURE__ */ jsx(
717
+ EmojiPicker,
718
+ {
719
+ labels: emojiPickerLabels,
720
+ locale: emojiLocale,
721
+ emojiImageBaseUrl,
722
+ onSelect: insertEmoji,
723
+ trigger: (() => {
724
+ const index = locked ? -1 : enabledCount.value++;
725
+ return /* @__PURE__ */ jsx(
726
+ "button",
727
+ {
728
+ type: "button",
729
+ "data-tool": "emoji",
730
+ className: "uix-rich-text__tool",
731
+ "aria-label": labels.emoji,
732
+ title: labels.emoji,
733
+ disabled: locked,
734
+ tabIndex: index === rovingIndex ? 0 : -1,
735
+ onFocus: () => {
736
+ if (index >= 0) setRovingIndex(index);
737
+ },
738
+ onMouseDown: (event) => event.preventDefault(),
739
+ children: /* @__PURE__ */ jsx(EditorIcon, { name: "emoji" })
740
+ }
741
+ );
742
+ })()
743
+ }
744
+ ) }) : null,
745
+ trailing.length ? /* @__PURE__ */ jsx("div", { className: "uix-rich-text__group uix-rich-text__group--end", role: "group", children: trailing.map(renderTool) }) : null,
746
+ has("link") ? /* @__PURE__ */ jsx(Popover, { id: linkPopoverId, anchor: linkButtonRef, className: "uix-rich-text__link", role: "dialog", "aria-label": labels.link, children: /* @__PURE__ */ jsxs(
747
+ "form",
748
+ {
749
+ className: "uix-rich-text__link-form",
750
+ onSubmit: (event) => {
751
+ event.preventDefault();
752
+ applyLink();
753
+ },
754
+ onKeyDown: (event) => {
755
+ if (event.key === "Escape") {
756
+ event.preventDefault();
757
+ closeLink();
758
+ }
759
+ },
760
+ children: [
761
+ /* @__PURE__ */ jsxs("div", { className: "uix-field", children: [
762
+ /* @__PURE__ */ jsx("label", { className: "uix-field__label", htmlFor: `${uid}-link-url`, children: labels.linkUrl }),
763
+ /* @__PURE__ */ jsx(
764
+ "input",
765
+ {
766
+ ref: linkInputRef,
767
+ id: `${uid}-link-url`,
768
+ className: "uix-input",
769
+ type: "text",
770
+ inputMode: "url",
771
+ autoComplete: "url",
772
+ value: linkUrl,
773
+ "aria-invalid": linkError || void 0,
774
+ "aria-describedby": linkError ? `${uid}-link-error` : void 0,
775
+ onChange: (event) => {
776
+ setLinkUrl(event.target.value);
777
+ setLinkError(false);
778
+ }
779
+ }
780
+ ),
781
+ linkError ? /* @__PURE__ */ jsx("div", { className: "uix-field__msg", children: /* @__PURE__ */ jsx("span", { id: `${uid}-link-error`, className: "uix-field__error", role: "alert", children: labels.linkInvalid }) }) : null
782
+ ] }),
783
+ /* @__PURE__ */ jsxs("div", { className: "uix-rich-text__link-actions", children: [
784
+ state?.link ? /* @__PURE__ */ jsx(
785
+ "button",
786
+ {
787
+ type: "button",
788
+ className: "uix-btn uix-btn--ghost uix-btn--sm",
789
+ onClick: () => {
790
+ editor?.chain().focus().extendMarkRange("link").unsetLink().run();
791
+ closeLink();
792
+ },
793
+ children: labels.linkRemove
794
+ }
795
+ ) : null,
796
+ /* @__PURE__ */ jsx("button", { type: "submit", className: "uix-btn uix-btn--primary uix-btn--sm", children: labels.linkApply })
797
+ ] })
798
+ ]
799
+ }
800
+ ) }) : null,
801
+ has("image") ? /* @__PURE__ */ jsx(
802
+ "input",
803
+ {
804
+ ref: fileRef,
805
+ type: "file",
806
+ accept: "image/*",
807
+ hidden: true,
808
+ tabIndex: -1,
809
+ "aria-hidden": "true",
810
+ onChange: (event) => {
811
+ const file = event.target.files?.[0];
812
+ event.target.value = "";
813
+ if (file && editor) void uploadImage(editor, file);
814
+ }
815
+ }
816
+ ) : null
817
+ ]
818
+ }
819
+ );
820
+ const surface = /* @__PURE__ */ jsxs("div", { className: "uix-rich-text__surface", children: [
821
+ mode === "source" ? /* @__PURE__ */ jsx(
822
+ Textarea,
823
+ {
824
+ ref: sourceRef,
825
+ id,
826
+ value: markdown,
827
+ onChange: (event) => emit(event.target.value),
828
+ onBlur,
829
+ onKeyDown: (event) => {
830
+ if (onSubmitShortcut && (event.metaKey || event.ctrlKey) && event.key === "Enter") {
831
+ event.preventDefault();
832
+ onSubmitShortcut();
833
+ }
834
+ },
835
+ disabled,
836
+ readOnly,
837
+ placeholder,
838
+ rows,
839
+ spellCheck: false,
840
+ className: "uix-rich-text__source",
841
+ "aria-label": props["aria-label"],
842
+ "aria-labelledby": props["aria-labelledby"],
843
+ "aria-describedby": describedBy,
844
+ "aria-invalid": invalid || void 0,
845
+ "aria-required": props["aria-required"] || void 0
846
+ }
847
+ ) : editor ? /* @__PURE__ */ jsx(EditorContent, { editor, className: "uix-rich-text__editor" }) : (
848
+ // One frame before Tiptap mounts: the same content, read-only, without layout shift.
849
+ /* @__PURE__ */ jsx("div", { className: "uix-rich-text__content uix-rich-text__content--loading", "aria-busy": "true", style: { minHeight: `calc(${rows} * 1lh + 2 * var(--uix-space-3))` }, children: /* @__PURE__ */ jsx(Markdown, { isSafeUrl, resolveImageSrc, children: value }) })
850
+ ),
851
+ suggestOpen && suggest ? /* @__PURE__ */ jsx(
852
+ EmojiSuggestions,
853
+ {
854
+ id: suggestId,
855
+ label: labels.emojiSuggestions,
856
+ items: suggest.items,
857
+ query: suggest.query,
858
+ active: suggestIndex,
859
+ rect: suggest.rect,
860
+ onPick: (item) => suggest.command(item),
861
+ renderEmoji
862
+ }
863
+ ) : null
864
+ ] });
865
+ const counter = maxLength !== void 0 ? /* @__PURE__ */ jsx("span", { id: counterId, className: "uix-rich-text__counter", "data-over": over || void 0, children: formatLabel(labels.characterCount, { count: markdown.length, max: maxLength }) }) : null;
866
+ const statusLine = /* @__PURE__ */ jsx("span", { id: statusId, className: "uix-rich-text__status", role: "status", "data-kind": status?.kind, children: status?.text ?? "" });
867
+ const hidden = name ? /* @__PURE__ */ jsx("input", { type: "hidden", name, value: markdown }) : null;
868
+ const stateAttrs = {
869
+ "data-disabled": disabled || void 0,
870
+ "data-readonly": readOnly || void 0,
871
+ "data-invalid": invalid || void 0,
872
+ "data-mode": mode
873
+ };
874
+ if (variant === "composer") {
875
+ return /* @__PURE__ */ jsxs(Composer, { className: cx("uix-rich-text", "uix-rich-text--composer", className), ...stateAttrs, children: [
876
+ surface,
877
+ /* @__PURE__ */ jsxs(ComposerBar, { className: "uix-rich-text__bar", children: [
878
+ toolbar,
879
+ statusLine,
880
+ counter,
881
+ toolbarEnd
882
+ ] }),
883
+ hidden
884
+ ] });
885
+ }
886
+ return /* @__PURE__ */ jsxs("div", { className: cx("uix-rich-text", className), ...stateAttrs, children: [
887
+ toolbar,
888
+ surface,
889
+ /* @__PURE__ */ jsxs("div", { className: "uix-rich-text__footer", children: [
890
+ statusLine,
891
+ counter,
892
+ toolbarEnd
893
+ ] }),
894
+ hidden
895
+ ] });
896
+ }
897
+ function EmojiSuggestions({ id, label, items, query, active, rect, onPick, renderEmoji }) {
898
+ const listRef = useRef(null);
899
+ const [pos, setPos] = useState(null);
900
+ useEffect(() => {
901
+ const el = listRef.current;
902
+ if (!el || !rect || typeof window === "undefined") return;
903
+ const box = el.getBoundingClientRect();
904
+ const result = computePosition(
905
+ { x: rect.left, y: rect.top, width: rect.width, height: rect.height },
906
+ { width: box.width, height: box.height },
907
+ { width: window.innerWidth, height: window.innerHeight },
908
+ { placement: "bottom-start", offset: 4, padding: 8 }
909
+ );
910
+ setPos({ x: result.x, y: result.y });
911
+ }, [rect, items.length]);
912
+ useEffect(() => {
913
+ document.getElementById(`${id}-${active}`)?.scrollIntoView?.({ block: "nearest" });
914
+ }, [active, id]);
915
+ return /* @__PURE__ */ jsx(
916
+ "div",
917
+ {
918
+ ref: listRef,
919
+ id,
920
+ role: "listbox",
921
+ "aria-label": label,
922
+ className: "uix-rich-text__suggest",
923
+ style: { left: pos?.x ?? -9999, top: pos?.y ?? -9999 },
924
+ children: items.map((item, i) => /* @__PURE__ */ jsxs(
925
+ "div",
926
+ {
927
+ id: `${id}-${i}`,
928
+ role: "option",
929
+ "aria-selected": i === active,
930
+ className: "uix-rich-text__suggest-item",
931
+ onMouseDown: (event) => {
932
+ event.preventDefault();
933
+ onPick(item);
934
+ },
935
+ children: [
936
+ /* @__PURE__ */ jsx("span", { className: "uix-rich-text__suggest-emoji", "aria-hidden": "true", children: renderEmoji(item.emoji ?? "") }),
937
+ /* @__PURE__ */ jsxs("span", { className: "uix-rich-text__suggest-name", children: [
938
+ ":",
939
+ item.shortcodes.find((s) => s.startsWith(query.toLowerCase())) ?? item.shortcodes[0] ?? item.name,
940
+ ":"
941
+ ] })
942
+ ]
943
+ },
944
+ item.name
945
+ ))
946
+ }
947
+ );
948
+ }
949
+ export {
950
+ DEFAULT_RICH_TEXT_LABELS,
951
+ RichTextEditor,
952
+ RichTextEditorFallback,
953
+ suggestEmoji
954
+ };