@exsto/ds 0.0.1-rc.1 → 0.0.1-rc.13

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 (150) hide show
  1. package/README.md +108 -27
  2. package/dist/chunks/date-range-picker-CWVcHgy2.js +204 -0
  3. package/dist/chunks/date-range-picker-CWVcHgy2.js.map +1 -0
  4. package/dist/chunks/input-syWxjY2U.js +172 -0
  5. package/dist/chunks/input-syWxjY2U.js.map +1 -0
  6. package/dist/chunks/toggle-Bxg8P-61.js +490 -0
  7. package/dist/chunks/toggle-Bxg8P-61.js.map +1 -0
  8. package/dist/date-picker.js +345 -0
  9. package/dist/date-picker.js.map +1 -0
  10. package/dist/ds/src/date-picker.d.ts +9 -0
  11. package/dist/ds/src/index.d.ts +2 -3
  12. package/dist/ds/src/rich-text-editor.d.ts +8 -0
  13. package/dist/index.js +4514 -2
  14. package/dist/index.js.map +1 -0
  15. package/dist/rich-text-editor.js +905 -0
  16. package/dist/rich-text-editor.js.map +1 -0
  17. package/dist/source-hash.txt +1 -0
  18. package/dist/styles.css +938 -235
  19. package/dist/theme.css +308 -0
  20. package/dist/theme.css.d.ts +2 -0
  21. package/dist/ui/src/components/edyn/animated-number/animated-number.d.ts +30 -0
  22. package/dist/ui/src/components/edyn/animated-number/animated-number.test.d.ts +9 -0
  23. package/dist/ui/src/components/edyn/animated-number/index.d.ts +1 -0
  24. package/dist/ui/src/components/edyn/audio-player/audio-player.d.ts +3 -2
  25. package/dist/ui/src/components/edyn/date-picker/date-picker.d.ts +76 -0
  26. package/dist/ui/src/components/edyn/date-picker/date-picker.state.test.d.ts +9 -0
  27. package/dist/ui/src/components/edyn/date-picker/date-text.d.ts +40 -0
  28. package/dist/ui/src/components/edyn/date-picker/index.d.ts +2 -0
  29. package/dist/ui/src/components/edyn/date-range-picker/date-range-picker.d.ts +28 -0
  30. package/dist/ui/src/components/edyn/date-range-picker/date-range-picker.test.d.ts +9 -0
  31. package/dist/ui/src/components/edyn/date-range-picker/index.d.ts +1 -0
  32. package/dist/ui/src/components/edyn/initials-avatar/index.d.ts +1 -0
  33. package/dist/ui/src/components/edyn/initials-avatar/initials-avatar.d.ts +54 -0
  34. package/dist/ui/src/components/edyn/initials-avatar/initials-avatar.test.d.ts +10 -0
  35. package/dist/ui/src/components/edyn/multi-select/index.d.ts +1 -0
  36. package/dist/ui/src/components/edyn/multi-select/multi-select.d.ts +106 -0
  37. package/dist/ui/src/components/edyn/multi-select/multi-select.test.d.ts +9 -0
  38. package/dist/ui/src/components/edyn/progress-line/index.d.ts +1 -0
  39. package/dist/ui/src/components/edyn/progress-line/progress-line.d.ts +23 -0
  40. package/dist/ui/src/components/edyn/progress-line/progress-line.test.d.ts +9 -0
  41. package/dist/ui/src/components/edyn/rich-text-content/allow-list.d.ts +11 -0
  42. package/dist/ui/src/components/edyn/rich-text-content/index.d.ts +2 -0
  43. package/dist/ui/src/components/edyn/rich-text-content/rich-text-content.d.ts +22 -0
  44. package/dist/ui/src/components/edyn/rich-text-content/rich-text-content.test.d.ts +8 -0
  45. package/dist/ui/src/components/edyn/rich-text-editor/extensions.d.ts +18 -0
  46. package/dist/ui/src/components/edyn/rich-text-editor/index.d.ts +3 -0
  47. package/dist/ui/src/components/edyn/rich-text-editor/mention-list.d.ts +17 -0
  48. package/dist/ui/src/components/edyn/rich-text-editor/rich-text-editor.d.ts +53 -0
  49. package/dist/ui/src/components/edyn/rich-text-editor/rich-text-editor.test.d.ts +6 -0
  50. package/dist/ui/src/components/edyn/rich-text-editor/serialize.d.ts +13 -0
  51. package/dist/ui/src/components/edyn/rich-text-editor/sync.d.ts +19 -0
  52. package/dist/ui/src/components/edyn/rich-text-editor/sync.test.d.ts +11 -0
  53. package/dist/ui/src/components/edyn/rich-text-editor/toolbar.d.ts +20 -0
  54. package/dist/ui/src/components/edyn/rich-text-editor/types.d.ts +68 -0
  55. package/dist/ui/src/components/edyn/video-player/video-player.d.ts +2 -2
  56. package/dist/ui/src/components/forwarded-props.test.d.ts +8 -0
  57. package/dist/ui/src/components/ui/accordion/accordion.d.ts +9 -8
  58. package/dist/ui/src/components/ui/attachment/attachment.d.ts +1 -1
  59. package/dist/ui/src/components/ui/badge/badge.d.ts +1 -1
  60. package/dist/ui/src/components/ui/button/button.behavior.test.d.ts +8 -0
  61. package/dist/ui/src/components/ui/button/button.d.ts +1 -1
  62. package/dist/ui/src/components/ui/command/command.d.ts +17 -1
  63. package/dist/ui/src/components/ui/dropdown-menu/dropdown-menu.d.ts +12 -1
  64. package/dist/ui/src/components/ui/input-group/input-group.d.ts +1 -1
  65. package/dist/ui/src/components/ui/item/item.d.ts +1 -1
  66. package/dist/ui/src/components/ui/pagination/pagination.d.ts +3 -2
  67. package/dist/ui/src/components/ui/popover/popover.d.ts +1 -1
  68. package/dist/ui/src/components/ui/progress/progress.behavior.test.d.ts +8 -0
  69. package/dist/ui/src/components/ui/progress/progress.d.ts +43 -1
  70. package/dist/ui/src/components/ui/scroll-area/scroll-area.behavior.test.d.ts +12 -0
  71. package/dist/ui/src/components/ui/scroll-area/scroll-area.d.ts +59 -6
  72. package/dist/ui/src/components/ui/separator/separator.behavior.test.d.ts +14 -0
  73. package/dist/ui/src/components/ui/separator/separator.d.ts +14 -0
  74. package/dist/ui/src/components/ui/table/table.behavior.test.d.ts +4 -5
  75. package/dist/ui/src/components/ui/table/table.d.ts +8 -1
  76. package/dist/ui/src/components/ui/tabs/tabs.d.ts +9 -8
  77. package/dist/ui/src/index.d.ts +6 -0
  78. package/dist/ui/src/lib/first-weekday.d.ts +6 -0
  79. package/dist/ui/src/lib/focus-ring.test.d.ts +8 -0
  80. package/dist/ui/src/lib/popup-list.d.ts +53 -0
  81. package/dist/ui/src/lib/popup-list.test.d.ts +10 -0
  82. package/package.json +32 -8
  83. package/dist/chunks/toggle-group-ByNq8g7H.js +0 -4263
  84. package/dist/chunks/toggle-group-ByNq8g7H.js.map +0 -1
  85. package/dist/dom/src/context.d.ts +0 -34
  86. package/dist/dom/src/define-element.d.ts +0 -11
  87. package/dist/dom/src/field-control.d.ts +0 -5
  88. package/dist/dom/src/index.d.ts +0 -6
  89. package/dist/dom/src/mode.d.ts +0 -8
  90. package/dist/dom/src/styles.d.ts +0 -6
  91. package/dist/dom/src/theme-state.d.ts +0 -1
  92. package/dist/dom/src/types.d.ts +0 -65
  93. package/dist/ds/src/elements.d.ts +0 -12
  94. package/dist/elements/src/edx-accordion.d.ts +0 -14
  95. package/dist/elements/src/edx-accordion.test.d.ts +0 -1
  96. package/dist/elements/src/edx-alert.d.ts +0 -14
  97. package/dist/elements/src/edx-audio-player.d.ts +0 -11
  98. package/dist/elements/src/edx-avatar.d.ts +0 -10
  99. package/dist/elements/src/edx-badge.d.ts +0 -12
  100. package/dist/elements/src/edx-breadcrumb.d.ts +0 -11
  101. package/dist/elements/src/edx-button-group.d.ts +0 -7
  102. package/dist/elements/src/edx-button.d.ts +0 -20
  103. package/dist/elements/src/edx-calendar.d.ts +0 -21
  104. package/dist/elements/src/edx-card.d.ts +0 -8
  105. package/dist/elements/src/edx-checkbox.d.ts +0 -18
  106. package/dist/elements/src/edx-combobox.d.ts +0 -28
  107. package/dist/elements/src/edx-content-components.d.ts +0 -2
  108. package/dist/elements/src/edx-dialog.d.ts +0 -20
  109. package/dist/elements/src/edx-drawer.d.ts +0 -21
  110. package/dist/elements/src/edx-dropdown-menu.d.ts +0 -29
  111. package/dist/elements/src/edx-field.d.ts +0 -9
  112. package/dist/elements/src/edx-field.test.d.ts +0 -1
  113. package/dist/elements/src/edx-indicator.d.ts +0 -14
  114. package/dist/elements/src/edx-input-group.d.ts +0 -32
  115. package/dist/elements/src/edx-input-otp.d.ts +0 -19
  116. package/dist/elements/src/edx-input.d.ts +0 -31
  117. package/dist/elements/src/edx-interactive-components.d.ts +0 -2
  118. package/dist/elements/src/edx-label.d.ts +0 -6
  119. package/dist/elements/src/edx-pagination.d.ts +0 -8
  120. package/dist/elements/src/edx-popover.d.ts +0 -24
  121. package/dist/elements/src/edx-progress.d.ts +0 -10
  122. package/dist/elements/src/edx-radio-group.d.ts +0 -22
  123. package/dist/elements/src/edx-radio-group.test.d.ts +0 -1
  124. package/dist/elements/src/edx-scroll-area.d.ts +0 -15
  125. package/dist/elements/src/edx-scroll-area.test.d.ts +0 -1
  126. package/dist/elements/src/edx-select.d.ts +0 -34
  127. package/dist/elements/src/edx-separator.d.ts +0 -10
  128. package/dist/elements/src/edx-skeleton.d.ts +0 -8
  129. package/dist/elements/src/edx-slider.d.ts +0 -17
  130. package/dist/elements/src/edx-slider.test.d.ts +0 -1
  131. package/dist/elements/src/edx-switch.d.ts +0 -18
  132. package/dist/elements/src/edx-table.d.ts +0 -13
  133. package/dist/elements/src/edx-table.test.d.ts +0 -1
  134. package/dist/elements/src/edx-tabs.d.ts +0 -15
  135. package/dist/elements/src/edx-tabs.test.d.ts +0 -1
  136. package/dist/elements/src/edx-textarea.d.ts +0 -25
  137. package/dist/elements/src/edx-toggle-group.d.ts +0 -27
  138. package/dist/elements/src/edx-toggle-group.test.d.ts +0 -1
  139. package/dist/elements/src/edx-toggle.d.ts +0 -18
  140. package/dist/elements/src/edx-tooltip.d.ts +0 -28
  141. package/dist/elements/src/edx-video-player.d.ts +0 -16
  142. package/dist/elements/src/field-control-bridge.d.ts +0 -7
  143. package/dist/elements/src/index.d.ts +0 -14
  144. package/dist/elements/src/projected-content.test.d.ts +0 -1
  145. package/dist/elements/src/registry.d.ts +0 -39
  146. package/dist/elements/src/registry.test.d.ts +0 -1
  147. package/dist/elements.js +0 -4020
  148. package/dist/elements.js.map +0 -1
  149. /package/dist/{dom/src/define-element.test.d.ts → ui/src/components/edyn/date-picker/date-text.test.d.ts} +0 -0
  150. /package/dist/{dom/src/native-slot.test.d.ts → ui/src/lib/first-weekday.test.d.ts} +0 -0
@@ -0,0 +1,905 @@
1
+ import { c as Button, n as Popover, r as PopoverContent, s as PopoverTrigger, t as Input, u as cn } from "./chunks/input-syWxjY2U.js";
2
+ import { C as RICH_TEXT_ELEMENTS, O as popupItemClass, S as RICH_TEXT_ATTRIBUTES, T as isAllowedRichTextHref, _ as DropdownMenuTrigger, a as DropdownMenuContent, k as ScrollArea, n as toggleVariants, r as DropdownMenu, s as DropdownMenuItem, v as RICH_TEXT_BODY_CLASS, w as RICH_TEXT_LINK_PROTOCOLS } from "./chunks/toggle-Bxg8P-61.js";
3
+ import { useCallback, useEffect, useId, useImperativeHandle, useMemo, useReducer, useRef, useState } from "react";
4
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
+ import { IconBold, IconItalic, IconLink, IconList, IconListNumbers, IconLoader2, IconSeparatorHorizontal, IconUnderline, IconVariable } from "@tabler/icons-react";
6
+ import { EditorContent, useEditor, useEditorState } from "@tiptap/react";
7
+ import { Extension, mergeAttributes } from "@tiptap/core";
8
+ import { Bold } from "@tiptap/extension-bold";
9
+ import { Document } from "@tiptap/extension-document";
10
+ import { HardBreak } from "@tiptap/extension-hard-break";
11
+ import { HorizontalRule } from "@tiptap/extension-horizontal-rule";
12
+ import { Italic } from "@tiptap/extension-italic";
13
+ import { Link } from "@tiptap/extension-link";
14
+ import { BulletList, ListItem, OrderedList } from "@tiptap/extension-list";
15
+ import { Mention } from "@tiptap/extension-mention";
16
+ import { Paragraph } from "@tiptap/extension-paragraph";
17
+ import { Text } from "@tiptap/extension-text";
18
+ import { Underline } from "@tiptap/extension-underline";
19
+ import { UndoRedo } from "@tiptap/extensions";
20
+ import { Plugin, PluginKey, TextSelection } from "@tiptap/pm/state";
21
+ import { DOMSerializer } from "@tiptap/pm/model";
22
+ import { Toolbar } from "@base-ui/react/toolbar";
23
+ //#region ../ui/src/components/edyn/rich-text-editor/serialize.ts
24
+ var MENTION_MARK = "data-rich-text-mention";
25
+ var defaultMentionToText = (item) => `@${item.id}`;
26
+ /**
27
+ * The document as HTML. Not `editor.getHTML()`: that would write a mention as its chip element, and
28
+ * the HTML must carry the plain text the app chose (`@person@example.test`) and nothing else.
29
+ */
30
+ var serializeDocument = (doc, toText) => {
31
+ const schema = doc.type.schema;
32
+ const nodes = {
33
+ ...DOMSerializer.nodesFromSchema(schema),
34
+ mention: (node) => [
35
+ "span",
36
+ { [MENTION_MARK]: "" },
37
+ toText({
38
+ id: String(node.attrs.id ?? ""),
39
+ label: String(node.attrs.label ?? "")
40
+ })
41
+ ]
42
+ };
43
+ const serializer = new DOMSerializer(nodes, DOMSerializer.marksFromSchema(schema));
44
+ const container = document.createElement("div");
45
+ container.appendChild(serializer.serializeFragment(doc.content));
46
+ for (const element of Array.from(container.querySelectorAll(`[${MENTION_MARK}]`))) element.replaceWith(document.createTextNode(element.textContent ?? ""));
47
+ return container.innerHTML;
48
+ };
49
+ /** No text and no mention. A document of blank paragraphs, spaces and line breaks is empty too. */
50
+ var isDocumentEmpty = (doc) => {
51
+ if (doc.textContent.trim() !== "") return false;
52
+ let hasMention = false;
53
+ doc.descendants((node) => {
54
+ if (node.type.name === "mention") hasMention = true;
55
+ return !hasMention;
56
+ });
57
+ return !hasMention;
58
+ };
59
+ /** Characters the HTML text will have: a mention counts as the text it is written as. */
60
+ var countCharacters = (doc, toText) => {
61
+ let total = 0;
62
+ doc.descendants((node) => {
63
+ if (node.isText) total += node.text?.length ?? 0;
64
+ else if (node.type.name === "mention") total += toText({
65
+ id: String(node.attrs.id ?? ""),
66
+ label: String(node.attrs.label ?? "")
67
+ }).length;
68
+ else if (node.type.name === "hardBreak") total += 1;
69
+ return true;
70
+ });
71
+ return total;
72
+ };
73
+ //#endregion
74
+ //#region ../ui/src/components/edyn/rich-text-editor/extensions.ts
75
+ var MENTION_CHIP_CLASS = "rounded-sm bg-primary/10 px-1 py-0.5 font-medium text-primary";
76
+ var limitKey = new PluginKey("richTextMaxLength");
77
+ /** Refuses an edit that would take the text over the limit (a paste included) and lets one that shortens it through. */
78
+ var maxLengthExtension = (host) => Extension.create({
79
+ name: "maxLength",
80
+ addProseMirrorPlugins() {
81
+ return [new Plugin({
82
+ key: limitKey,
83
+ filterTransaction: (tr, state) => {
84
+ const limit = host.maxLength();
85
+ if (!tr.docChanged || limit === void 0) return true;
86
+ const next = countCharacters(tr.doc, host.toText());
87
+ return next <= limit || next <= countCharacters(state.doc, host.toText());
88
+ }
89
+ })];
90
+ }
91
+ });
92
+ /**
93
+ * The editor's whole schema: paragraph, line break, bold, italic, underline, bullet and numbered lists, link, and the
94
+ * mention chip. Whatever else arrives (a paste, `value`) is not in the schema, so ProseMirror drops the markup and keeps
95
+ * the text.
96
+ */
97
+ var buildExtensions = (host) => [
98
+ Document,
99
+ Paragraph,
100
+ Text,
101
+ HardBreak,
102
+ Bold,
103
+ Italic,
104
+ Underline,
105
+ BulletList,
106
+ OrderedList.extend({ addAttributes() {
107
+ return {};
108
+ } }),
109
+ ListItem,
110
+ HorizontalRule,
111
+ Link.extend({ addAttributes() {
112
+ return { href: {
113
+ default: null,
114
+ parseHTML: (element) => element.getAttribute("href"),
115
+ renderHTML: (attributes) => attributes.href ? { href: attributes.href } : {}
116
+ } };
117
+ } }).configure({
118
+ openOnClick: false,
119
+ autolink: true,
120
+ linkOnPaste: true,
121
+ defaultProtocol: "https",
122
+ protocols: ["mailto"],
123
+ isAllowedUri: (url) => isAllowedRichTextHref(url),
124
+ HTMLAttributes: {
125
+ rel: null,
126
+ target: null,
127
+ class: null
128
+ }
129
+ }),
130
+ UndoRedo,
131
+ maxLengthExtension(host),
132
+ Mention.extend({ addAttributes() {
133
+ return {
134
+ id: {
135
+ default: null,
136
+ parseHTML: (element) => element.getAttribute("data-id"),
137
+ renderHTML: (attributes) => attributes.id ? { "data-id": attributes.id } : {}
138
+ },
139
+ label: {
140
+ default: null,
141
+ parseHTML: (element) => element.getAttribute("data-label"),
142
+ renderHTML: (attributes) => attributes.label ? { "data-label": attributes.label } : {}
143
+ }
144
+ };
145
+ } }).configure({
146
+ HTMLAttributes: { class: MENTION_CHIP_CLASS },
147
+ renderText: ({ node }) => `@${node.attrs.label ?? node.attrs.id}`,
148
+ renderHTML: ({ options, node }) => [
149
+ "span",
150
+ mergeAttributes(options.HTMLAttributes),
151
+ `@${node.attrs.label ?? node.attrs.id}`
152
+ ],
153
+ suggestion: {
154
+ char: "@",
155
+ allow: () => host.mentionEnabled(),
156
+ items: () => [],
157
+ command: ({ editor, range, props }) => {
158
+ editor.chain().focus().insertContentAt(range, [{
159
+ type: "mention",
160
+ attrs: {
161
+ id: props.id,
162
+ label: props.label
163
+ }
164
+ }, {
165
+ type: "text",
166
+ text: " "
167
+ }]).run();
168
+ },
169
+ render: () => ({
170
+ onStart: (props) => host.onSuggestion(props),
171
+ onUpdate: (props) => host.onSuggestion(props),
172
+ onKeyDown: (props) => host.onSuggestionKey(props),
173
+ onExit: () => host.onSuggestion(null)
174
+ })
175
+ }
176
+ })
177
+ ];
178
+ //#endregion
179
+ //#region ../ui/src/components/edyn/rich-text-editor/mention-list.tsx
180
+ var FIRST = {
181
+ status: "loading",
182
+ items: [],
183
+ page: 1,
184
+ hasMore: false,
185
+ more: false,
186
+ active: 0
187
+ };
188
+ var toPage = (result) => Array.isArray(result) ? {
189
+ items: result,
190
+ hasMore: false
191
+ } : result;
192
+ var optionId = (listId, index) => `${listId}-${index}`;
193
+ /** The list that opens under the caret when the writer types @. */
194
+ var MentionList = ({ query, rect, config, labels, listId, onPick, keyHandler, onActiveChange }) => {
195
+ const [state, setState] = useState(FIRST);
196
+ const refSearch = useRef(config.search);
197
+ refSearch.current = config.search;
198
+ const refFirst = useRef(true);
199
+ const refController = useRef(null);
200
+ const refViewport = useRef(null);
201
+ const debounce = config.debounce ?? 250;
202
+ useEffect(() => {
203
+ const controller = new AbortController();
204
+ refController.current = controller;
205
+ setState((previous) => ({
206
+ ...previous,
207
+ status: "loading",
208
+ page: 1,
209
+ more: false
210
+ }));
211
+ const timer = setTimeout(async () => {
212
+ try {
213
+ const page = toPage(await refSearch.current(query, {
214
+ page: 1,
215
+ signal: controller.signal
216
+ }));
217
+ if (controller.signal.aborted) return;
218
+ setState({
219
+ status: "ready",
220
+ items: page.items,
221
+ page: 1,
222
+ hasMore: page.hasMore === true,
223
+ more: false,
224
+ active: 0
225
+ });
226
+ } catch {
227
+ if (!controller.signal.aborted) setState((previous) => ({
228
+ ...previous,
229
+ status: "error",
230
+ items: [],
231
+ hasMore: false
232
+ }));
233
+ }
234
+ }, refFirst.current ? 0 : debounce);
235
+ refFirst.current = false;
236
+ return () => {
237
+ clearTimeout(timer);
238
+ controller.abort();
239
+ };
240
+ }, [query, debounce]);
241
+ const loadMore = () => {
242
+ const controller = refController.current;
243
+ if (!controller || state.status !== "ready" || !state.hasMore || state.more) return;
244
+ const next = state.page + 1;
245
+ setState((previous) => ({
246
+ ...previous,
247
+ more: true
248
+ }));
249
+ refSearch.current(query, {
250
+ page: next,
251
+ signal: controller.signal
252
+ }).then((result) => {
253
+ if (controller.signal.aborted) return;
254
+ const page = toPage(result);
255
+ setState((previous) => ({
256
+ ...previous,
257
+ items: [...previous.items, ...page.items.filter((item) => !previous.items.some((known) => known.id === item.id))],
258
+ page: next,
259
+ hasMore: page.hasMore === true,
260
+ more: false
261
+ }));
262
+ }).catch(() => {
263
+ if (!controller.signal.aborted) setState((previous) => ({
264
+ ...previous,
265
+ more: false,
266
+ hasMore: false
267
+ }));
268
+ });
269
+ };
270
+ const refLoadMore = useRef(loadMore);
271
+ refLoadMore.current = loadMore;
272
+ const activeItem = state.items[state.active];
273
+ useEffect(() => {
274
+ onActiveChange(activeItem ? optionId(listId, state.active) : void 0);
275
+ }, [
276
+ activeItem,
277
+ listId,
278
+ onActiveChange,
279
+ state.active
280
+ ]);
281
+ useEffect(() => {
282
+ document.getElementById(optionId(listId, state.active))?.scrollIntoView({ block: "nearest" });
283
+ }, [listId, state.active]);
284
+ const watchSentinel = useCallback((node) => {
285
+ if (!node) return;
286
+ const observer = new IntersectionObserver((entries) => {
287
+ if (entries.some((entry) => entry.isIntersecting)) refLoadMore.current();
288
+ }, { root: refViewport.current });
289
+ observer.observe(node);
290
+ return () => observer.disconnect();
291
+ }, []);
292
+ useEffect(() => {
293
+ keyHandler.current = (event) => {
294
+ const count = state.items.length;
295
+ if (event.key === "Escape") {
296
+ event.stopPropagation();
297
+ return false;
298
+ }
299
+ if (count === 0) return false;
300
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
301
+ const step = event.key === "ArrowDown" ? 1 : -1;
302
+ const next = (state.active + step + count) % count;
303
+ if (step === 1 && state.active === count - 1) refLoadMore.current();
304
+ setState((previous) => ({
305
+ ...previous,
306
+ active: next
307
+ }));
308
+ return true;
309
+ }
310
+ if ((event.key === "Enter" || event.key === "Tab") && activeItem) {
311
+ onPick(activeItem);
312
+ return true;
313
+ }
314
+ return false;
315
+ };
316
+ return () => {
317
+ keyHandler.current = null;
318
+ };
319
+ }, [
320
+ activeItem,
321
+ keyHandler,
322
+ onPick,
323
+ state.active,
324
+ state.items.length
325
+ ]);
326
+ const anchor = { getBoundingClientRect: () => rect?.() ?? new DOMRect(0, 0, 0, 0) };
327
+ const announcement = state.status === "loading" ? labels.mentionSearching : state.status === "error" ? labels.mentionError : state.items.length === 0 ? labels.mentionEmpty : labels.mentionResults.replace("{count}", String(state.items.length));
328
+ return /* @__PURE__ */ jsx(Popover, {
329
+ open: true,
330
+ onOpenChange: () => void 0,
331
+ children: /* @__PURE__ */ jsxs(PopoverContent, {
332
+ anchor,
333
+ align: "start",
334
+ side: "bottom",
335
+ sideOffset: 6,
336
+ initialFocus: false,
337
+ finalFocus: false,
338
+ "data-slot": "rich-text-mention-popup",
339
+ className: "w-72 gap-0 p-1",
340
+ children: [/* @__PURE__ */ jsxs(ScrollArea, {
341
+ className: "max-h-60",
342
+ viewportRef: refViewport,
343
+ children: [
344
+ /* @__PURE__ */ jsx("div", {
345
+ role: "listbox",
346
+ id: listId,
347
+ "aria-label": labels.mentionList,
348
+ className: cn("flex flex-col gap-px", state.items.length === 0 && "hidden"),
349
+ children: state.items.map((item, index) => /* @__PURE__ */ jsxs("div", {
350
+ id: optionId(listId, index),
351
+ role: "option",
352
+ "aria-selected": index === state.active,
353
+ "data-slot": "rich-text-mention-option",
354
+ "data-highlighted": index === state.active ? "" : void 0,
355
+ className: cn(popupItemClass("base-ui"), "h-auto min-h-8 flex-col items-start gap-0"),
356
+ onMouseDown: (event) => event.preventDefault(),
357
+ onMouseMove: () => setState((previous) => previous.active === index ? previous : {
358
+ ...previous,
359
+ active: index
360
+ }),
361
+ onClick: () => onPick(item),
362
+ children: [/* @__PURE__ */ jsx("span", {
363
+ className: "truncate",
364
+ children: item.label
365
+ }), item.secondary ? /* @__PURE__ */ jsx("span", {
366
+ className: "truncate text-xs text-foreground/70",
367
+ children: item.secondary
368
+ }) : null]
369
+ }, item.id))
370
+ }),
371
+ state.status === "loading" && state.items.length === 0 ? /* @__PURE__ */ jsxs("div", {
372
+ className: "flex items-center gap-2 px-2 py-1.5 text-sm text-muted-foreground",
373
+ children: [/* @__PURE__ */ jsx(IconLoader2, {
374
+ className: "size-4 animate-spin motion-reduce:animate-none",
375
+ "aria-hidden": true
376
+ }), labels.mentionSearching]
377
+ }) : null,
378
+ state.status === "ready" && state.items.length === 0 ? /* @__PURE__ */ jsx("div", {
379
+ className: "px-2 py-1.5 text-sm text-muted-foreground",
380
+ children: labels.mentionEmpty
381
+ }) : null,
382
+ state.status === "error" ? /* @__PURE__ */ jsx("div", {
383
+ className: "px-2 py-1.5 text-sm text-destructive",
384
+ children: labels.mentionError
385
+ }) : null,
386
+ state.hasMore ? /* @__PURE__ */ jsx("div", {
387
+ ref: watchSentinel,
388
+ className: "flex items-center gap-2 px-2 py-1.5 text-xs text-muted-foreground",
389
+ children: state.more ? labels.mentionLoadingMore : null
390
+ }, state.items.length) : null
391
+ ]
392
+ }), /* @__PURE__ */ jsx("div", {
393
+ role: "status",
394
+ className: "sr-only",
395
+ children: announcement
396
+ })]
397
+ })
398
+ });
399
+ };
400
+ //#endregion
401
+ //#region ../ui/src/components/edyn/rich-text-editor/sync.ts
402
+ /**
403
+ * Whether commands may be called on the editor. TipTap builds an editor during the render, mounts its view later, and
404
+ * destroys one that has not been mounted within a moment (useEditor then makes a new one), so an effect can be handed an
405
+ * editor that is not there yet or no longer is. `isDestroyed` is true for both: it reads true until the view exists. A
406
+ * destroyed editor has no command manager, so `editor.commands` throws on it.
407
+ */
408
+ var isLive = (editor) => editor.isInitialized && !editor.isDestroyed;
409
+ /**
410
+ * Brings the editor to a value the parent passed in, if it is one the editor did not just report itself.
411
+ * `known` is the last HTML the editor and the parent agree on (the starting value, then whatever the editor emitted or was
412
+ * given). The caret stays where it was, kept inside the new content, so a value changed from outside while the writer is
413
+ * typing does not throw the caret to the start.
414
+ */
415
+ var syncValue = (editor, value, known) => {
416
+ if (!isLive(editor) || value === void 0 || value === known.current) return;
417
+ known.current = value;
418
+ const { from, to } = editor.state.selection;
419
+ editor.commands.setContent(value, { emitUpdate: false });
420
+ editor.commands.command(({ tr }) => {
421
+ const size = tr.doc.content.size;
422
+ tr.setSelection(TextSelection.between(tr.doc.resolve(Math.min(from, size)), tr.doc.resolve(Math.min(to, size))));
423
+ return true;
424
+ });
425
+ };
426
+ /** Sets whether the editor can be edited without telling the app something changed (TipTap emits an update by default). */
427
+ var syncEditable = (editor, editable) => {
428
+ if (isLive(editor) && editor.isEditable !== editable) editor.setEditable(editable, false);
429
+ };
430
+ //#endregion
431
+ //#region ../ui/src/components/edyn/rich-text-editor/toolbar.tsx
432
+ var BUTTON_CLASS = cn(toggleVariants({
433
+ variant: "default",
434
+ size: "sm"
435
+ }), "size-7 px-0");
436
+ /** A typed address becomes a link: "name@example.test" a mailto, "example.test" an https address. */
437
+ var normalizeHref = (raw) => {
438
+ const value = raw.trim();
439
+ if (value === "" || /^[a-z][a-z0-9+.-]*:/i.test(value)) return value;
440
+ return /^[^\s@/]+@[^\s@/]+\.[^\s@/]+$/.test(value) ? `mailto:${value}` : `https://${value}`;
441
+ };
442
+ var ToolbarButton = ({ label, pressed, disabled, onClick, children }) => /* @__PURE__ */ jsx(Toolbar.Button, {
443
+ type: "button",
444
+ "aria-label": label,
445
+ title: label,
446
+ "aria-pressed": pressed,
447
+ "data-pressed": pressed ? "" : void 0,
448
+ disabled,
449
+ className: BUTTON_CLASS,
450
+ onMouseDown: (event) => event.preventDefault(),
451
+ onClick,
452
+ children
453
+ });
454
+ var hrefAt = (editor, range) => {
455
+ const { doc } = editor.state;
456
+ const marks = range.from < range.to ? doc.nodeAt(range.from)?.marks ?? [] : doc.resolve(range.from).marks();
457
+ return String(marks.find((mark) => mark.type.name === "link")?.attrs.href ?? "");
458
+ };
459
+ var LinkControl = ({ range, editor, labels, active, disabled, open, onOpenChange }) => {
460
+ const [address, setAddress] = useState("");
461
+ const [invalid, setInvalid] = useState(false);
462
+ useEffect(() => {
463
+ if (open) {
464
+ setAddress(range.current ? hrefAt(editor, range.current) : "");
465
+ setInvalid(false);
466
+ }
467
+ }, [
468
+ editor,
469
+ open,
470
+ range
471
+ ]);
472
+ const apply = (event) => {
473
+ event.preventDefault();
474
+ event.stopPropagation();
475
+ const href = normalizeHref(address);
476
+ if (!isAllowedRichTextHref(href)) {
477
+ setInvalid(true);
478
+ return;
479
+ }
480
+ const saved = range.current ?? {
481
+ from: editor.state.selection.from,
482
+ to: editor.state.selection.to
483
+ };
484
+ const chain = editor.chain().focus().setTextSelection(saved);
485
+ if (saved.from === saved.to && !hrefAt(editor, saved)) chain.insertContent({
486
+ type: "text",
487
+ text: href,
488
+ marks: [{
489
+ type: "link",
490
+ attrs: { href }
491
+ }]
492
+ }).run();
493
+ else chain.extendMarkRange("link").setLink({ href }).run();
494
+ onOpenChange(false);
495
+ };
496
+ const remove = () => {
497
+ const saved = range.current ?? {
498
+ from: editor.state.selection.from,
499
+ to: editor.state.selection.to
500
+ };
501
+ editor.chain().focus().setTextSelection(saved).extendMarkRange("link").unsetLink().run();
502
+ onOpenChange(false);
503
+ };
504
+ return /* @__PURE__ */ jsxs(Popover, {
505
+ open,
506
+ onOpenChange,
507
+ children: [/* @__PURE__ */ jsx(PopoverTrigger, {
508
+ render: /* @__PURE__ */ jsx(Toolbar.Button, {
509
+ type: "button",
510
+ "aria-label": labels.link,
511
+ title: labels.link,
512
+ "aria-pressed": active,
513
+ "data-pressed": active ? "" : void 0,
514
+ disabled,
515
+ className: BUTTON_CLASS
516
+ }),
517
+ children: /* @__PURE__ */ jsx(IconLink, { "aria-hidden": true })
518
+ }), /* @__PURE__ */ jsx(PopoverContent, {
519
+ align: "start",
520
+ className: "w-80",
521
+ children: /* @__PURE__ */ jsxs("form", {
522
+ className: "flex flex-col gap-2",
523
+ onSubmit: apply,
524
+ children: [
525
+ /* @__PURE__ */ jsx(Input, {
526
+ type: "text",
527
+ inputMode: "url",
528
+ "aria-label": labels.linkAddress,
529
+ "aria-invalid": invalid || void 0,
530
+ value: address,
531
+ onChange: (event) => {
532
+ setAddress(event.target.value);
533
+ setInvalid(false);
534
+ }
535
+ }),
536
+ invalid ? /* @__PURE__ */ jsx("p", {
537
+ className: "text-xs text-destructive",
538
+ children: labels.linkInvalid
539
+ }) : null,
540
+ /* @__PURE__ */ jsxs("div", {
541
+ className: "flex justify-end gap-2",
542
+ children: [active ? /* @__PURE__ */ jsx(Button, {
543
+ type: "button",
544
+ variant: "outline",
545
+ size: "sm",
546
+ onClick: remove,
547
+ children: labels.linkRemove
548
+ }) : null, /* @__PURE__ */ jsx(Button, {
549
+ type: "submit",
550
+ size: "sm",
551
+ children: labels.linkApply
552
+ })]
553
+ })
554
+ ]
555
+ })
556
+ })]
557
+ });
558
+ };
559
+ var RichTextToolbar = ({ linkRange, editor, labels, fields, disabled, linkOpen, onLinkOpenChange }) => {
560
+ const state = useEditorState({
561
+ editor,
562
+ selector: ({ editor: current }) => ({
563
+ bold: current.isActive("bold"),
564
+ italic: current.isActive("italic"),
565
+ underline: current.isActive("underline"),
566
+ bullets: current.isActive("bulletList"),
567
+ numbers: current.isActive("orderedList"),
568
+ link: current.isActive("link")
569
+ })
570
+ });
571
+ return /* @__PURE__ */ jsxs(Toolbar.Root, {
572
+ "data-slot": "rich-text-toolbar",
573
+ "aria-label": labels.toolbar,
574
+ className: "flex flex-wrap items-center gap-0.5 border-b border-input p-1",
575
+ children: [
576
+ /* @__PURE__ */ jsx(ToolbarButton, {
577
+ label: labels.bold,
578
+ pressed: state.bold,
579
+ disabled,
580
+ onClick: () => editor.chain().focus().toggleBold().run(),
581
+ children: /* @__PURE__ */ jsx(IconBold, { "aria-hidden": true })
582
+ }),
583
+ /* @__PURE__ */ jsx(ToolbarButton, {
584
+ label: labels.italic,
585
+ pressed: state.italic,
586
+ disabled,
587
+ onClick: () => editor.chain().focus().toggleItalic().run(),
588
+ children: /* @__PURE__ */ jsx(IconItalic, { "aria-hidden": true })
589
+ }),
590
+ /* @__PURE__ */ jsx(ToolbarButton, {
591
+ label: labels.underline,
592
+ pressed: state.underline,
593
+ disabled,
594
+ onClick: () => editor.chain().focus().toggleUnderline().run(),
595
+ children: /* @__PURE__ */ jsx(IconUnderline, { "aria-hidden": true })
596
+ }),
597
+ /* @__PURE__ */ jsx(Toolbar.Separator, { className: "mx-1 h-4 w-px bg-border" }),
598
+ /* @__PURE__ */ jsx(ToolbarButton, {
599
+ label: labels.bulletList,
600
+ pressed: state.bullets,
601
+ disabled,
602
+ onClick: () => editor.chain().focus().toggleBulletList().run(),
603
+ children: /* @__PURE__ */ jsx(IconList, { "aria-hidden": true })
604
+ }),
605
+ /* @__PURE__ */ jsx(ToolbarButton, {
606
+ label: labels.numberedList,
607
+ pressed: state.numbers,
608
+ disabled,
609
+ onClick: () => editor.chain().focus().toggleOrderedList().run(),
610
+ children: /* @__PURE__ */ jsx(IconListNumbers, { "aria-hidden": true })
611
+ }),
612
+ /* @__PURE__ */ jsx(LinkControl, {
613
+ range: linkRange,
614
+ editor,
615
+ labels,
616
+ active: state.link,
617
+ disabled,
618
+ open: linkOpen,
619
+ onOpenChange: onLinkOpenChange
620
+ }),
621
+ /* @__PURE__ */ jsx(ToolbarButton, {
622
+ label: labels.horizontalRule,
623
+ disabled,
624
+ onClick: () => editor.chain().focus().setHorizontalRule().run(),
625
+ children: /* @__PURE__ */ jsx(IconSeparatorHorizontal, { "aria-hidden": true })
626
+ }),
627
+ fields && fields.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Toolbar.Separator, { className: "mx-1 h-4 w-px bg-border" }), /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
628
+ render: /* @__PURE__ */ jsx(Toolbar.Button, {
629
+ type: "button",
630
+ "aria-label": labels.insertField,
631
+ title: labels.insertField,
632
+ disabled,
633
+ className: BUTTON_CLASS
634
+ }),
635
+ children: /* @__PURE__ */ jsx(IconVariable, { "aria-hidden": true })
636
+ }), /* @__PURE__ */ jsx(DropdownMenuContent, {
637
+ align: "start",
638
+ className: "w-56",
639
+ children: fields.map((field) => /* @__PURE__ */ jsx(DropdownMenuItem, {
640
+ onClick: () => editor.chain().focus().insertContent({
641
+ type: "text",
642
+ text: `{{${field.id}}}`
643
+ }).run(),
644
+ children: field.label
645
+ }, field.id))
646
+ })] })] }) : null
647
+ ]
648
+ });
649
+ };
650
+ //#endregion
651
+ //#region ../ui/src/components/edyn/rich-text-editor/types.ts
652
+ var DEFAULT_RICH_TEXT_LABELS = {
653
+ toolbar: "Formatting",
654
+ bold: "Bold",
655
+ italic: "Italic",
656
+ underline: "Underline",
657
+ bulletList: "Bulleted list",
658
+ numberedList: "Numbered list",
659
+ link: "Link",
660
+ horizontalRule: "Horizontal line",
661
+ insertField: "Insert a field",
662
+ linkAddress: "Link address",
663
+ linkApply: "Apply",
664
+ linkRemove: "Remove link",
665
+ linkInvalid: "Enter an http, https or mailto address.",
666
+ mentionList: "Mentions",
667
+ mentionSearching: "Searching...",
668
+ mentionEmpty: "No matches",
669
+ mentionError: "The search failed",
670
+ mentionLoadingMore: "Loading more...",
671
+ mentionResults: "{count} results"
672
+ };
673
+ //#endregion
674
+ //#region ../ui/src/components/edyn/rich-text-editor/rich-text-editor.tsx
675
+ var LINE = 1.5;
676
+ /**
677
+ * A small rich-text field: paragraphs, bold, italic, underline, bulleted and numbered lists and links, with optional
678
+ * @mentions the app answers asynchronously. The value is HTML in and out, and only the elements in `RICH_TEXT_ELEMENTS`
679
+ * can come out (no class, no style), whatever is pasted. A mention is a chip while editing and plain text in the HTML.
680
+ * The server should still sanitise: this is a convenience for the writer, not a security boundary.
681
+ */
682
+ var RichTextEditor = ({ value, defaultValue = "", onChange, placeholder, disabled = false, readOnly = false, invalid = false, minRows = 6, maxRows = 20, maxLength, mention, fields, toolbar = true, labels, id, autoFocus = false, onPasteFiles, ref, className, "aria-invalid": ariaInvalid, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, ...props }) => {
683
+ const isInvalid = invalid || ariaInvalid === true || ariaInvalid === "true";
684
+ const text = useMemo(() => ({
685
+ ...DEFAULT_RICH_TEXT_LABELS,
686
+ ...labels
687
+ }), [labels]);
688
+ const listId = useId();
689
+ const [suggestion, setSuggestion] = useState(null);
690
+ const [activeOption, setActiveOption] = useState(void 0);
691
+ const [linkOpen, setLinkOpen] = useState(false);
692
+ const refLinkRange = useRef(null);
693
+ const refKeys = useRef(null);
694
+ const refKnown = useRef(value ?? defaultValue);
695
+ const refPending = useRef([]);
696
+ const [, wake] = useReducer((count) => count + 1, 0);
697
+ const editable = !disabled && !readOnly;
698
+ const showToolbar = toolbar && !readOnly;
699
+ const latest = useRef({
700
+ mention,
701
+ maxLength,
702
+ onChange,
703
+ onPasteFiles,
704
+ toolbar: showToolbar
705
+ });
706
+ latest.current = {
707
+ mention,
708
+ maxLength,
709
+ onChange,
710
+ onPasteFiles,
711
+ toolbar: showToolbar
712
+ };
713
+ const host = useMemo(() => ({
714
+ mentionEnabled: () => latest.current.mention !== void 0,
715
+ maxLength: () => latest.current.maxLength,
716
+ toText: () => latest.current.mention?.toText ?? defaultMentionToText,
717
+ onSuggestion: (props) => {
718
+ if (props === null) setActiveOption(void 0);
719
+ setSuggestion(props === null ? null : {
720
+ query: props.query,
721
+ rect: props.clientRect ?? null,
722
+ command: props.command
723
+ });
724
+ },
725
+ onSuggestionKey: ({ event }) => refKeys.current?.(event) ?? false
726
+ }), []);
727
+ const extensions = useMemo(() => buildExtensions(host), [host]);
728
+ const emit = useCallback((current) => {
729
+ const html = isDocumentEmpty(current.state.doc) ? "" : serializeDocument(current.state.doc, host.toText());
730
+ refKnown.current = html;
731
+ return html;
732
+ }, [host]);
733
+ const attributes = useMemo(() => {
734
+ const entries = {
735
+ role: "textbox",
736
+ "aria-multiline": "true",
737
+ "aria-autocomplete": mention ? "list" : void 0,
738
+ "aria-controls": suggestion ? listId : void 0,
739
+ "aria-activedescendant": suggestion ? activeOption : void 0,
740
+ "aria-label": ariaLabel,
741
+ "aria-labelledby": ariaLabelledBy,
742
+ "aria-describedby": ariaDescribedBy,
743
+ "aria-invalid": isInvalid ? "true" : void 0,
744
+ "aria-readonly": readOnly ? "true" : void 0,
745
+ "aria-disabled": disabled ? "true" : void 0,
746
+ "aria-placeholder": placeholder,
747
+ tabindex: readOnly && !disabled ? "0" : void 0,
748
+ id,
749
+ "data-slot": "rich-text-editor-content",
750
+ class: cn(RICH_TEXT_BODY_CLASS, "min-h-(--rich-text-min) px-2.5 py-2 outline-none")
751
+ };
752
+ return Object.fromEntries(Object.entries(entries).filter((entry) => entry[1] !== void 0));
753
+ }, [
754
+ activeOption,
755
+ ariaDescribedBy,
756
+ ariaLabel,
757
+ ariaLabelledBy,
758
+ disabled,
759
+ id,
760
+ isInvalid,
761
+ listId,
762
+ mention,
763
+ placeholder,
764
+ readOnly,
765
+ suggestion
766
+ ]);
767
+ const editor = useEditor({
768
+ extensions,
769
+ content: value ?? defaultValue,
770
+ editable,
771
+ autofocus: autoFocus ? "end" : false,
772
+ editorProps: {
773
+ attributes,
774
+ handlePaste: (_view, event) => {
775
+ const data = event.clipboardData;
776
+ if (!(data !== null && data.files.length > 0 && data.getData("text/plain") === "" && data.getData("text/html") === "") || !latest.current.onPasteFiles) return false;
777
+ latest.current.onPasteFiles(Array.from(data.files));
778
+ return true;
779
+ },
780
+ handleKeyDown: (view, event) => {
781
+ if ((event.metaKey || event.ctrlKey) && !event.altKey && !event.shiftKey && event.key.toLowerCase() === "k" && latest.current.toolbar) {
782
+ event.preventDefault();
783
+ refLinkRange.current = {
784
+ from: view.state.selection.from,
785
+ to: view.state.selection.to
786
+ };
787
+ setLinkOpen(true);
788
+ return true;
789
+ }
790
+ return false;
791
+ }
792
+ },
793
+ onBlur: () => {
794
+ setSuggestion(null);
795
+ setActiveOption(void 0);
796
+ },
797
+ onUpdate: ({ editor: current }) => {
798
+ latest.current.onChange?.(emit(current));
799
+ }
800
+ });
801
+ const ready = isLive(editor);
802
+ useEffect(() => {
803
+ editor.on("create", wake);
804
+ wake();
805
+ return () => {
806
+ editor.off("create", wake);
807
+ };
808
+ }, [editor]);
809
+ const whenLive = useCallback((action) => {
810
+ if (isLive(editor)) action(editor);
811
+ else refPending.current.push(action);
812
+ }, [editor]);
813
+ useImperativeHandle(ref, () => ({
814
+ insertText: (text) => {
815
+ whenLive((live) => {
816
+ live.chain().focus().insertContent({
817
+ type: "text",
818
+ text
819
+ }).run();
820
+ });
821
+ },
822
+ focus: () => {
823
+ whenLive((live) => {
824
+ live.commands.focus();
825
+ });
826
+ }
827
+ }), [whenLive]);
828
+ useEffect(() => {
829
+ if (!ready || !isLive(editor)) return;
830
+ for (const action of refPending.current.splice(0)) action(editor);
831
+ }, [editor, ready]);
832
+ useEffect(() => {
833
+ if (ready) syncEditable(editor, editable);
834
+ }, [
835
+ editable,
836
+ editor,
837
+ ready
838
+ ]);
839
+ useEffect(() => {
840
+ if (ready) syncValue(editor, value, refKnown);
841
+ }, [
842
+ editor,
843
+ ready,
844
+ value
845
+ ]);
846
+ const empty = useEditorState({
847
+ editor,
848
+ selector: ({ editor: current }) => isDocumentEmpty(current.state.doc)
849
+ });
850
+ const pick = useCallback((item) => {
851
+ suggestion?.command(item);
852
+ }, [suggestion]);
853
+ return /* @__PURE__ */ jsxs("div", {
854
+ "data-slot": "rich-text-editor",
855
+ "data-invalid": isInvalid ? "" : void 0,
856
+ "data-disabled": disabled ? "" : void 0,
857
+ "data-readonly": readOnly ? "" : void 0,
858
+ className: cn("flex w-full min-w-0 flex-col rounded-lg border border-input bg-transparent text-base transition-colors focus-within:border-ring focus-within:ring-3 focus-within:ring-ring/50 data-disabled:pointer-events-none data-disabled:cursor-not-allowed data-disabled:bg-input/50 data-disabled:opacity-50 data-invalid:border-destructive data-invalid:ring-3 data-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:data-disabled:bg-input/80 dark:data-invalid:ring-destructive/40", className),
859
+ ...props,
860
+ children: [
861
+ showToolbar ? /* @__PURE__ */ jsx(RichTextToolbar, {
862
+ editor,
863
+ labels: text,
864
+ fields,
865
+ disabled,
866
+ linkOpen,
867
+ linkRange: refLinkRange,
868
+ onLinkOpenChange: (next) => {
869
+ if (next && !linkOpen) refLinkRange.current = {
870
+ from: editor.state.selection.from,
871
+ to: editor.state.selection.to
872
+ };
873
+ setLinkOpen(next);
874
+ }
875
+ }) : null,
876
+ /* @__PURE__ */ jsxs(ScrollArea, {
877
+ className: "relative",
878
+ style: {
879
+ maxHeight: `${maxRows * LINE + 1}rem`,
880
+ ["--rich-text-min"]: `${minRows * LINE + 1}rem`
881
+ },
882
+ focusable: false,
883
+ children: [empty && placeholder ? /* @__PURE__ */ jsx("p", {
884
+ "aria-hidden": true,
885
+ className: "pointer-events-none absolute start-2.5 top-2 text-muted-foreground",
886
+ children: placeholder
887
+ }) : null, /* @__PURE__ */ jsx(EditorContent, { editor })]
888
+ }),
889
+ suggestion && mention ? /* @__PURE__ */ jsx(MentionList, {
890
+ query: suggestion.query,
891
+ rect: suggestion.rect,
892
+ config: mention,
893
+ labels: text,
894
+ listId,
895
+ onPick: pick,
896
+ keyHandler: refKeys,
897
+ onActiveChange: setActiveOption
898
+ }) : null
899
+ ]
900
+ });
901
+ };
902
+ //#endregion
903
+ export { DEFAULT_RICH_TEXT_LABELS, RICH_TEXT_ATTRIBUTES, RICH_TEXT_ELEMENTS, RICH_TEXT_LINK_PROTOCOLS, RichTextEditor };
904
+
905
+ //# sourceMappingURL=rich-text-editor.js.map