@mieweb/ui 0.11.0-dev.20 → 0.11.0-dev.22

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 (42) hide show
  1. package/dist/chunk-5EOW6AP3.js +1163 -0
  2. package/dist/chunk-5EOW6AP3.js.map +1 -0
  3. package/dist/chunk-7BA7ENN5.cjs +1188 -0
  4. package/dist/chunk-7BA7ENN5.cjs.map +1 -0
  5. package/dist/{chunk-JUJGWUAN.js → chunk-BHKGVFTY.js} +26 -3
  6. package/dist/chunk-BHKGVFTY.js.map +1 -0
  7. package/dist/{chunk-WSMBIXPU.cjs → chunk-GFP3BUVC.cjs} +4 -4
  8. package/dist/{chunk-WSMBIXPU.cjs.map → chunk-GFP3BUVC.cjs.map} +1 -1
  9. package/dist/{chunk-EBQG3NGT.js → chunk-MC467GS4.js} +29 -3
  10. package/dist/chunk-MC467GS4.js.map +1 -0
  11. package/dist/{chunk-SHBCUXFS.cjs → chunk-PRGQVUMU.cjs} +26 -3
  12. package/dist/chunk-PRGQVUMU.cjs.map +1 -0
  13. package/dist/{chunk-LAN5VWGC.js → chunk-UQPNO5YC.js} +3 -3
  14. package/dist/{chunk-LAN5VWGC.js.map → chunk-UQPNO5YC.js.map} +1 -1
  15. package/dist/{chunk-5K7PRPSV.cjs → chunk-XWNTCOC7.cjs} +29 -3
  16. package/dist/chunk-XWNTCOC7.cjs.map +1 -0
  17. package/dist/components/Accordion/index.cjs +2 -2
  18. package/dist/components/Accordion/index.d.cts +7 -0
  19. package/dist/components/Accordion/index.d.ts +7 -0
  20. package/dist/components/Accordion/index.js +1 -1
  21. package/dist/components/EmailEditor/index.cjs +12 -11
  22. package/dist/components/EmailEditor/index.js +3 -2
  23. package/dist/components/RichTextEditor/index.cjs +5 -4
  24. package/dist/components/RichTextEditor/index.d.cts +89 -0
  25. package/dist/components/RichTextEditor/index.d.ts +89 -0
  26. package/dist/components/RichTextEditor/index.js +2 -1
  27. package/dist/index.cjs +23 -19
  28. package/dist/index.d.cts +105 -2
  29. package/dist/index.d.ts +105 -2
  30. package/dist/index.js +4 -4
  31. package/dist/styles.css +1 -1
  32. package/dist/tailwind-preset.cjs +4 -4
  33. package/dist/tailwind-preset.js +1 -1
  34. package/package.json +1 -1
  35. package/dist/chunk-5K7PRPSV.cjs.map +0 -1
  36. package/dist/chunk-ATRW3J5Z.cjs +0 -567
  37. package/dist/chunk-ATRW3J5Z.cjs.map +0 -1
  38. package/dist/chunk-EBQG3NGT.js.map +0 -1
  39. package/dist/chunk-JUJGWUAN.js.map +0 -1
  40. package/dist/chunk-LX323IMT.js +0 -543
  41. package/dist/chunk-LX323IMT.js.map +0 -1
  42. package/dist/chunk-SHBCUXFS.cjs.map +0 -1
@@ -0,0 +1,1188 @@
1
+ 'use strict';
2
+
3
+ var chunkYP5T6NZD_cjs = require('./chunk-YP5T6NZD.cjs');
4
+ var chunkPINIBEIU_cjs = require('./chunk-PINIBEIU.cjs');
5
+ var chunkC7RVKV25_cjs = require('./chunk-C7RVKV25.cjs');
6
+ var chunkZ5S5JEJD_cjs = require('./chunk-Z5S5JEJD.cjs');
7
+ var chunkL7L5S2HO_cjs = require('./chunk-L7L5S2HO.cjs');
8
+ var chunkANMX7YDL_cjs = require('./chunk-ANMX7YDL.cjs');
9
+ var chunkS5RVSXDV_cjs = require('./chunk-S5RVSXDV.cjs');
10
+ var React2 = require('react');
11
+ var lucideReact = require('lucide-react');
12
+ var jsxRuntime = require('react/jsx-runtime');
13
+
14
+ function _interopNamespace(e) {
15
+ if (e && e.__esModule) return e;
16
+ var n = Object.create(null);
17
+ if (e) {
18
+ Object.keys(e).forEach(function (k) {
19
+ if (k !== 'default') {
20
+ var d = Object.getOwnPropertyDescriptor(e, k);
21
+ Object.defineProperty(n, k, d.get ? d : {
22
+ enumerable: true,
23
+ get: function () { return e[k]; }
24
+ });
25
+ }
26
+ });
27
+ }
28
+ n.default = e;
29
+ return Object.freeze(n);
30
+ }
31
+
32
+ var React2__namespace = /*#__PURE__*/_interopNamespace(React2);
33
+
34
+ var DEFAULT_LABELS = {
35
+ insertInto: "Insert into",
36
+ macros: "Macros",
37
+ searchPlaceholder: "Search macros\u2026",
38
+ searchLabel: "Search macros",
39
+ close: "Close macros",
40
+ createMacro: "Save as macro",
41
+ empty: "No macros available",
42
+ emptyHint: "The application has not provided a macro list.",
43
+ noResults: "No macros match",
44
+ clearSearch: "Clear search",
45
+ showFullContent: "Show full content",
46
+ hideFullContent: "Hide full content",
47
+ more: "more",
48
+ less: "less"
49
+ };
50
+ function matchesQuery(macro, query) {
51
+ const q = query.toLowerCase();
52
+ return macro.label.toLowerCase().includes(q) || (macro.shortcut ?? "").toLowerCase().includes(q) || (macro.keywords ?? []).some((k) => k.toLowerCase().includes(q)) || macro.content.toLowerCase().includes(q);
53
+ }
54
+ function isShortcutHit(macro, query) {
55
+ return query.length > 0 && (macro.shortcut ?? "").toLowerCase().startsWith(query.toLowerCase());
56
+ }
57
+ function rankMacros(macros, query) {
58
+ return [...macros].sort(
59
+ (a, b) => Number(isShortcutHit(b, query)) - Number(isShortcutHit(a, query))
60
+ );
61
+ }
62
+ function MacroRow({
63
+ macro,
64
+ isPrevious,
65
+ isPeeked,
66
+ peekId,
67
+ labels,
68
+ onSelect,
69
+ onTogglePeek,
70
+ onRowKeyDown
71
+ }) {
72
+ const [truncated, setTruncated] = React2__namespace.useState(false);
73
+ const rowRef = React2__namespace.useRef(null);
74
+ const lessRef = React2__namespace.useRef(null);
75
+ const focusAfterToggle = React2__namespace.useRef(false);
76
+ React2__namespace.useEffect(() => {
77
+ if (!focusAfterToggle.current) return;
78
+ focusAfterToggle.current = false;
79
+ (isPeeked ? lessRef.current : rowRef.current)?.focus();
80
+ }, [isPeeked]);
81
+ const handleToggle = () => {
82
+ focusAfterToggle.current = true;
83
+ onTogglePeek();
84
+ };
85
+ const previewObserver = React2__namespace.useRef(null);
86
+ const measurePreview = (el) => {
87
+ previewObserver.current?.disconnect();
88
+ previewObserver.current = null;
89
+ if (!el) return;
90
+ const measure = () => setTruncated(el.scrollHeight > el.clientHeight);
91
+ measure();
92
+ if (typeof ResizeObserver !== "undefined") {
93
+ previewObserver.current = new ResizeObserver(measure);
94
+ previewObserver.current.observe(el);
95
+ }
96
+ };
97
+ return /* @__PURE__ */ jsxRuntime.jsxs(
98
+ "div",
99
+ {
100
+ "data-slot": "macro-library-item",
101
+ className: "group/macro hover:bg-muted/60 relative rounded-md",
102
+ children: [
103
+ /* @__PURE__ */ jsxRuntime.jsxs(
104
+ "button",
105
+ {
106
+ type: "button",
107
+ "data-macro-row": true,
108
+ ref: rowRef,
109
+ className: "focus-visible:ring-ring text-foreground hover:text-primary flex w-full cursor-pointer flex-col items-stretch gap-0.5 rounded-md px-2 py-1.5 text-start focus-visible:ring-2 focus-visible:outline-none",
110
+ onClick: () => onSelect(macro),
111
+ onKeyDown: onRowKeyDown,
112
+ children: [
113
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-2", children: [
114
+ macro.shortcut && /* @__PURE__ */ jsxRuntime.jsx(
115
+ "span",
116
+ {
117
+ dir: "ltr",
118
+ className: chunkS5RVSXDV_cjs.cn(
119
+ "border-border bg-background text-muted-foreground rounded border px-1 font-mono text-[11px]",
120
+ isPrevious && "border-warning-300 bg-warning-50 text-warning-800 dark:border-warning-700 dark:bg-warning-950 dark:text-warning-300"
121
+ ),
122
+ children: macro.shortcut
123
+ }
124
+ ),
125
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: macro.label })
126
+ ] }),
127
+ !isPeeked && /* @__PURE__ */ jsxRuntime.jsx(
128
+ "span",
129
+ {
130
+ ref: measurePreview,
131
+ className: "text-muted-foreground line-clamp-2 text-xs break-words",
132
+ children: macro.content
133
+ }
134
+ )
135
+ ]
136
+ }
137
+ ),
138
+ isPeeked && /* @__PURE__ */ jsxRuntime.jsx(
139
+ "div",
140
+ {
141
+ id: peekId,
142
+ "data-slot": "macro-library-peek",
143
+ className: "text-muted-foreground px-2 pb-1 text-xs break-words whitespace-pre-line",
144
+ children: macro.content
145
+ }
146
+ ),
147
+ truncated && !isPeeked && // Overlays the end of the clamped second line; revealed on row
148
+ // hover or keyboard focus so short rows stay entirely clean. Always
149
+ // visible on devices without hover (touch).
150
+ /* @__PURE__ */ jsxRuntime.jsxs(
151
+ "button",
152
+ {
153
+ type: "button",
154
+ className: "bg-muted text-muted-foreground hover:text-primary focus-visible:ring-ring invisible absolute end-1.5 bottom-1.5 flex cursor-pointer items-center rounded-sm px-1 text-[11px] leading-4 group-focus-within/macro:visible group-hover/macro:visible hover:underline focus-visible:ring-2 focus-visible:outline-none [@media(hover:none)]:visible",
155
+ "aria-expanded": false,
156
+ "aria-label": `${labels.showFullContent}: ${macro.label}`,
157
+ onClick: handleToggle,
158
+ children: [
159
+ labels.more,
160
+ /* @__PURE__ */ jsxRuntime.jsx(
161
+ chunkL7L5S2HO_cjs.ChevronRight,
162
+ {
163
+ "aria-hidden": "true",
164
+ className: "h-3 w-3 rtl:-scale-x-100"
165
+ }
166
+ )
167
+ ]
168
+ }
169
+ ),
170
+ isPeeked && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "-mt-1 flex justify-end px-2 pb-1", children: /* @__PURE__ */ jsxRuntime.jsx(
171
+ "button",
172
+ {
173
+ type: "button",
174
+ ref: lessRef,
175
+ className: "text-muted-foreground hover:text-primary focus-visible:ring-ring cursor-pointer rounded text-[11px] hover:underline focus-visible:ring-2 focus-visible:outline-none",
176
+ "aria-expanded": true,
177
+ "aria-controls": peekId,
178
+ "aria-label": `${labels.hideFullContent}: ${macro.label}`,
179
+ onClick: handleToggle,
180
+ children: labels.less
181
+ }
182
+ ) })
183
+ ]
184
+ }
185
+ );
186
+ }
187
+ var MacroLibrary = React2__namespace.forwardRef(
188
+ function MacroLibrary2({
189
+ groups,
190
+ onSelect,
191
+ id,
192
+ targetLabel,
193
+ onClose,
194
+ onCreateMacro,
195
+ autoFocusSearch = false,
196
+ className,
197
+ labels: labelOverrides
198
+ }, ref) {
199
+ const labels = { ...DEFAULT_LABELS, ...labelOverrides };
200
+ const [query, setQuery] = React2__namespace.useState("");
201
+ const [collapsed, setCollapsed] = React2__namespace.useState(
202
+ {}
203
+ );
204
+ const [peeked, setPeeked] = React2__namespace.useState(null);
205
+ const listRef = React2__namespace.useRef(null);
206
+ const searchRef = React2__namespace.useRef(null);
207
+ const panelId = React2__namespace.useId();
208
+ React2__namespace.useEffect(() => {
209
+ if (autoFocusSearch) searchRef.current?.focus();
210
+ }, [autoFocusSearch]);
211
+ const trimmed = query.trim();
212
+ const searching = trimmed.length > 0;
213
+ const visibleGroups = groups.map((group) => ({
214
+ ...group,
215
+ macros: searching ? rankMacros(
216
+ group.macros.filter((m) => matchesQuery(m, trimmed)),
217
+ trimmed
218
+ ) : group.macros
219
+ })).filter((group) => group.macros.length > 0).sort(
220
+ (a, b) => Number(b.macros.some((m) => isShortcutHit(m, trimmed))) - Number(a.macros.some((m) => isShortcutHit(m, trimmed)))
221
+ );
222
+ const hasMacros = groups.some((g) => g.macros.length > 0);
223
+ const noResults = hasMacros && searching && visibleGroups.length === 0;
224
+ const moveFocus = (delta) => {
225
+ const rows = Array.from(
226
+ listRef.current?.querySelectorAll(
227
+ "[data-macro-row]"
228
+ ) ?? []
229
+ );
230
+ if (rows.length === 0) return;
231
+ const index = rows.findIndex((row) => row === document.activeElement);
232
+ if (index === -1) {
233
+ rows[delta === 1 ? 0 : rows.length - 1].focus();
234
+ return;
235
+ }
236
+ const next = index + delta;
237
+ if (next < 0) {
238
+ searchRef.current?.focus();
239
+ return;
240
+ }
241
+ rows[Math.min(next, rows.length - 1)].focus();
242
+ };
243
+ const handlePanelKeyDown = (event) => {
244
+ if (event.nativeEvent.isComposing) return;
245
+ if (event.key === "Escape" && onClose) {
246
+ event.stopPropagation();
247
+ onClose();
248
+ }
249
+ };
250
+ const handleSearchKeyDown = (event) => {
251
+ if (event.nativeEvent.isComposing) return;
252
+ if (event.key === "ArrowDown") {
253
+ event.preventDefault();
254
+ moveFocus(1);
255
+ }
256
+ };
257
+ const handleRowKeyDown = (event) => {
258
+ if (event.nativeEvent.isComposing) return;
259
+ if (event.key === "ArrowDown") {
260
+ event.preventDefault();
261
+ moveFocus(1);
262
+ } else if (event.key === "ArrowUp") {
263
+ event.preventDefault();
264
+ moveFocus(-1);
265
+ }
266
+ };
267
+ return (
268
+ // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- keydown only delegates Escape-to-close from focusable children
269
+ /* @__PURE__ */ jsxRuntime.jsxs(
270
+ "div",
271
+ {
272
+ ref,
273
+ id,
274
+ role: "group",
275
+ "aria-label": targetLabel ? `${labels.insertInto} ${targetLabel}` : labels.macros,
276
+ "data-slot": "macro-library",
277
+ className: chunkS5RVSXDV_cjs.cn("bg-muted/20 flex min-h-0 flex-col", className),
278
+ onKeyDown: handlePanelKeyDown,
279
+ children: [
280
+ /* @__PURE__ */ jsxRuntime.jsxs(
281
+ "div",
282
+ {
283
+ "data-slot": "macro-library-header",
284
+ className: "border-border flex items-center justify-between gap-2 border-b px-3 py-2",
285
+ children: [
286
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground min-w-0 truncate text-xs", children: targetLabel ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
287
+ labels.insertInto,
288
+ " ",
289
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground font-semibold", children: targetLabel })
290
+ ] }) : labels.macros }),
291
+ onClose && /* @__PURE__ */ jsxRuntime.jsx(
292
+ chunkANMX7YDL_cjs.Button,
293
+ {
294
+ type: "button",
295
+ variant: "ghost",
296
+ size: "sm",
297
+ className: "h-6 w-6 shrink-0 p-0",
298
+ onClick: onClose,
299
+ "aria-label": labels.close,
300
+ children: /* @__PURE__ */ jsxRuntime.jsx(chunkL7L5S2HO_cjs.X, { className: "h-3.5 w-3.5" })
301
+ }
302
+ )
303
+ ]
304
+ }
305
+ ),
306
+ hasMacros ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
307
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 pt-2", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
308
+ /* @__PURE__ */ jsxRuntime.jsx(
309
+ chunkL7L5S2HO_cjs.Search,
310
+ {
311
+ "aria-hidden": "true",
312
+ className: "text-muted-foreground pointer-events-none absolute start-2.5 top-1/2 z-10 h-3.5 w-3.5 -translate-y-1/2"
313
+ }
314
+ ),
315
+ /* @__PURE__ */ jsxRuntime.jsx(
316
+ chunkZ5S5JEJD_cjs.Input,
317
+ {
318
+ ref: searchRef,
319
+ label: labels.searchLabel,
320
+ hideLabel: true,
321
+ size: "sm",
322
+ type: "search",
323
+ placeholder: labels.searchPlaceholder,
324
+ value: query,
325
+ onChange: (event) => setQuery(event.target.value),
326
+ onKeyDown: handleSearchKeyDown,
327
+ className: "ps-8",
328
+ "aria-controls": `${panelId}-list`
329
+ }
330
+ )
331
+ ] }) }),
332
+ /* @__PURE__ */ jsxRuntime.jsx("div", { role: "status", className: "sr-only", children: noResults ? `${labels.noResults} \u201C${trimmed}\u201D` : "" }),
333
+ /* @__PURE__ */ jsxRuntime.jsxs(
334
+ "div",
335
+ {
336
+ ref: listRef,
337
+ id: `${panelId}-list`,
338
+ "data-slot": "macro-library-list",
339
+ className: "min-h-0 flex-1 overflow-y-auto p-1.5",
340
+ children: [
341
+ noResults && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-muted-foreground px-2 py-8 text-center text-sm", children: [
342
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
343
+ labels.noResults,
344
+ " \u201C",
345
+ trimmed,
346
+ "\u201D"
347
+ ] }),
348
+ /* @__PURE__ */ jsxRuntime.jsx(
349
+ chunkANMX7YDL_cjs.Button,
350
+ {
351
+ type: "button",
352
+ variant: "ghost",
353
+ size: "sm",
354
+ className: "mt-2",
355
+ onClick: () => {
356
+ setQuery("");
357
+ searchRef.current?.focus();
358
+ },
359
+ children: labels.clearSearch
360
+ }
361
+ )
362
+ ] }),
363
+ visibleGroups.map((group) => {
364
+ const isPrevious = group.kind === "previous";
365
+ const isCollapsed = !searching && collapsed[group.id] === true;
366
+ const GroupIcon = isPrevious ? chunkL7L5S2HO_cjs.History : chunkL7L5S2HO_cjs.BookOpen;
367
+ return /* @__PURE__ */ jsxRuntime.jsxs(
368
+ "div",
369
+ {
370
+ "data-slot": "macro-library-group",
371
+ "data-kind": group.kind ?? "library",
372
+ children: [
373
+ /* @__PURE__ */ jsxRuntime.jsxs(
374
+ "button",
375
+ {
376
+ type: "button",
377
+ className: chunkS5RVSXDV_cjs.cn(
378
+ "text-muted-foreground hover:text-foreground flex w-full items-center gap-1.5 rounded px-2 pt-2.5 pb-1 text-start text-[11px] font-semibold tracking-wide uppercase",
379
+ isPrevious && "text-warning-700 dark:text-warning-400"
380
+ ),
381
+ "aria-expanded": !isCollapsed,
382
+ disabled: searching,
383
+ onClick: () => setCollapsed((prev) => ({
384
+ ...prev,
385
+ [group.id]: prev[group.id] !== true
386
+ })),
387
+ children: [
388
+ /* @__PURE__ */ jsxRuntime.jsx(GroupIcon, { "aria-hidden": "true", className: "h-3 w-3" }),
389
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate", children: group.label }),
390
+ !searching && (isCollapsed ? /* @__PURE__ */ jsxRuntime.jsx(
391
+ chunkL7L5S2HO_cjs.ChevronRight,
392
+ {
393
+ "aria-hidden": "true",
394
+ className: "h-3 w-3"
395
+ }
396
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
397
+ chunkL7L5S2HO_cjs.ChevronDown,
398
+ {
399
+ "aria-hidden": "true",
400
+ className: "h-3 w-3"
401
+ }
402
+ ))
403
+ ]
404
+ }
405
+ ),
406
+ !isCollapsed && group.macros.map((macro) => {
407
+ const isPeeked = peeked === macro.id;
408
+ return /* @__PURE__ */ jsxRuntime.jsx(
409
+ MacroRow,
410
+ {
411
+ macro,
412
+ isPrevious,
413
+ isPeeked,
414
+ peekId: `${panelId}-peek-${macro.id}`,
415
+ labels,
416
+ onSelect,
417
+ onTogglePeek: () => setPeeked(isPeeked ? null : macro.id),
418
+ onRowKeyDown: handleRowKeyDown
419
+ },
420
+ macro.id
421
+ );
422
+ })
423
+ ]
424
+ },
425
+ group.id
426
+ );
427
+ })
428
+ ]
429
+ }
430
+ )
431
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-muted-foreground flex flex-1 flex-col items-center justify-center gap-1 px-4 py-10 text-center text-sm", children: [
432
+ /* @__PURE__ */ jsxRuntime.jsx(chunkL7L5S2HO_cjs.BookOpen, { "aria-hidden": "true", className: "mb-1 h-5 w-5" }),
433
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-foreground font-medium", children: labels.empty }),
434
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs", children: labels.emptyHint })
435
+ ] }),
436
+ onCreateMacro && /* @__PURE__ */ jsxRuntime.jsx(
437
+ "div",
438
+ {
439
+ "data-slot": "macro-library-footer",
440
+ className: "border-border border-t px-2 py-1.5",
441
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
442
+ chunkANMX7YDL_cjs.Button,
443
+ {
444
+ type: "button",
445
+ variant: "ghost",
446
+ size: "sm",
447
+ className: "w-full justify-start gap-1.5",
448
+ onClick: onCreateMacro,
449
+ children: [
450
+ /* @__PURE__ */ jsxRuntime.jsx(chunkL7L5S2HO_cjs.Plus, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
451
+ labels.createMacro
452
+ ]
453
+ }
454
+ )
455
+ }
456
+ )
457
+ ]
458
+ }
459
+ )
460
+ );
461
+ }
462
+ );
463
+
464
+ // src/components/RichTextEditor/processDictation.ts
465
+ function processDictation(text) {
466
+ let result = text;
467
+ result = result.replace(
468
+ /\b(the|a|this|that|each|every|same|time|grace|trial|waiting|probationary|pay|billing|accounting)\s+period\b/gi,
469
+ "$1 period"
470
+ );
471
+ result = result.replace(
472
+ /\bperiod\s+(of|in|for|from|to|during|between|after|before|when|where|is|was|will|has|had)\b/gi,
473
+ "period $1"
474
+ );
475
+ result = result.replace(/\s+period(\s*$)/gi, ".$1");
476
+ result = result.replace(/\s+period\s+(?=[A-Z])/g, ". ");
477
+ result = result.replace(/\bcomma\s+(separated|delimited)/gi, "comma $1");
478
+ result = result.replace(/\s+comma\b/gi, ",");
479
+ result = result.replace(
480
+ /\b(the|a|my|your|his|her|their|semicolon|ascending|descending|transverse|sigmoid)\s+colon\b/gi,
481
+ "$1 colon"
482
+ );
483
+ result = result.replace(
484
+ /\bcolon\s+(cancer|surgery|health|polyp|scope|oscopy)/gi,
485
+ "colon $1"
486
+ );
487
+ result = result.replace(/\s+colon\b/gi, ":");
488
+ result = result.replace(
489
+ /\b(yard|meter|hundred|mad|quick|wild)\s+dash\b/gi,
490
+ "$1 dash"
491
+ );
492
+ result = result.replace(/\bdash\s+(to|for|of)\b/gi, "dash $1");
493
+ result = result.replace(/\s+dash\b/gi, " -");
494
+ const simplePunctuation = {
495
+ " full stop": ".",
496
+ " question mark": "?",
497
+ " exclamation point": "!",
498
+ " exclamation mark": "!",
499
+ " semicolon": ";",
500
+ " semi colon": ";",
501
+ " hyphen": "-",
502
+ " open parenthesis": " (",
503
+ " close parenthesis": ")",
504
+ " open bracket": " [",
505
+ " close bracket": "]",
506
+ " open quote": ' "',
507
+ " close quote": '"',
508
+ " quote": '"',
509
+ " apostrophe": "'",
510
+ " new line": "\n",
511
+ " newline": "\n",
512
+ " new paragraph": "\n\n",
513
+ " tab": " "
514
+ };
515
+ for (const [spoken, punctuation] of Object.entries(simplePunctuation)) {
516
+ const regex = new RegExp(spoken, "gi");
517
+ result = result.replace(regex, punctuation);
518
+ }
519
+ result = result.replace(
520
+ /([.!?])\s+([a-z])/g,
521
+ (_match, punct, letter) => `${punct} ${letter.toUpperCase()}`
522
+ );
523
+ return result;
524
+ }
525
+ function convertAngleBracketsToMustache(html) {
526
+ return html.replace(
527
+ /&lt;&lt;([^&]*)&gt;&gt;/g,
528
+ (_m, field) => `{{${field.trim()}}}`
529
+ ).replace(/<<([^>]*)>>/g, (_m, field) => `{{${field.trim()}}}`);
530
+ }
531
+ function getSpeechRecognition() {
532
+ const w = window;
533
+ return w.SpeechRecognition || w.webkitSpeechRecognition;
534
+ }
535
+ var BLOCK_BOUNDARY = /^(BR|DIV|P|LI|UL|OL|TABLE|TR|TD|TH|H[1-6]|BLOCKQUOTE|PRE|HR)$/;
536
+ function lastTextWithin(node) {
537
+ if (node.nodeType === Node.TEXT_NODE) {
538
+ return (node.textContent ?? "").length > 0 ? node : null;
539
+ }
540
+ if (node.nodeType !== Node.ELEMENT_NODE) return null;
541
+ if (BLOCK_BOUNDARY.test(node.tagName)) return "boundary";
542
+ for (let child = node.lastChild; child; child = child.previousSibling) {
543
+ const found = lastTextWithin(child);
544
+ if (found) return found;
545
+ }
546
+ return null;
547
+ }
548
+ function previousVisibleText(node, root) {
549
+ let current = node;
550
+ while (current !== root) {
551
+ let sibling = current.previousSibling;
552
+ while (sibling) {
553
+ const found = lastTextWithin(sibling);
554
+ if (found === "boundary") return null;
555
+ if (found) return found;
556
+ sibling = sibling.previousSibling;
557
+ }
558
+ const parent = current.parentNode;
559
+ if (!parent) return null;
560
+ if (parent !== root && parent.nodeType === Node.ELEMENT_NODE && BLOCK_BOUNDARY.test(parent.tagName)) {
561
+ return null;
562
+ }
563
+ current = parent;
564
+ }
565
+ return null;
566
+ }
567
+ var RichTextEditor = React2__namespace.forwardRef(
568
+ ({
569
+ value,
570
+ onChange,
571
+ placeholder,
572
+ className,
573
+ variableGroups,
574
+ enableDictation = true,
575
+ onDictationError,
576
+ disabled = false,
577
+ sanitizeHtml,
578
+ macros,
579
+ defaultMacrosOpen = false,
580
+ macroLabels,
581
+ onCreateMacro,
582
+ "aria-label": ariaLabel
583
+ }, ref) => {
584
+ const editorRef = React2__namespace.useRef(null);
585
+ const [isListening, setIsListening] = React2__namespace.useState(false);
586
+ const [speechSupported, setSpeechSupported] = React2__namespace.useState(false);
587
+ const recognitionRef = React2__namespace.useRef(null);
588
+ const isListeningRef = React2__namespace.useRef(false);
589
+ const shouldRestartRef = React2__namespace.useRef(false);
590
+ const [macrosOpen, setMacrosOpen] = React2__namespace.useState(defaultMacrosOpen);
591
+ const savedRangeRef = React2__namespace.useRef(null);
592
+ const macroPanelId = React2__namespace.useId();
593
+ const hasVariables = Boolean(variableGroups && variableGroups.length > 0);
594
+ const hasMacros = Boolean(macros);
595
+ const isEmpty = chunkC7RVKV25_cjs.isHtmlEmpty(value);
596
+ React2__namespace.useImperativeHandle(ref, () => editorRef.current);
597
+ React2__namespace.useEffect(() => {
598
+ setSpeechSupported(Boolean(getSpeechRecognition()));
599
+ }, []);
600
+ React2__namespace.useEffect(() => {
601
+ return () => {
602
+ if (recognitionRef.current) {
603
+ shouldRestartRef.current = false;
604
+ isListeningRef.current = false;
605
+ recognitionRef.current.stop();
606
+ }
607
+ };
608
+ }, []);
609
+ React2__namespace.useEffect(() => {
610
+ if (editorRef.current && editorRef.current.innerHTML !== value) {
611
+ editorRef.current.innerHTML = value;
612
+ }
613
+ }, [value]);
614
+ const updateContent = () => {
615
+ if (editorRef.current) onChange(editorRef.current.innerHTML);
616
+ };
617
+ const insertSanitized = (html) => {
618
+ if (!sanitizeHtml) return false;
619
+ document.execCommand("insertHTML", false, sanitizeHtml(html));
620
+ updateContent();
621
+ return true;
622
+ };
623
+ const handlePaste = (e) => {
624
+ const html = e.clipboardData.getData("text/html");
625
+ if (html && sanitizeHtml) {
626
+ e.preventDefault();
627
+ insertSanitized(html);
628
+ }
629
+ };
630
+ const handleDrop = (e) => {
631
+ const html = e.dataTransfer.getData("text/html");
632
+ if (!html || !sanitizeHtml) return;
633
+ e.preventDefault();
634
+ const caret = document.caretRangeFromPoint?.(e.clientX, e.clientY);
635
+ if (caret) {
636
+ const selection = window.getSelection();
637
+ selection?.removeAllRanges();
638
+ selection?.addRange(caret);
639
+ }
640
+ editorRef.current?.focus();
641
+ insertSanitized(html);
642
+ };
643
+ const execCommand = (command) => {
644
+ if (disabled) return;
645
+ document.execCommand(command, false);
646
+ editorRef.current?.focus();
647
+ updateContent();
648
+ };
649
+ const handleConvertToMustache = () => {
650
+ if (disabled || !editorRef.current) return;
651
+ editorRef.current.innerHTML = convertAngleBracketsToMustache(
652
+ editorRef.current.innerHTML
653
+ );
654
+ updateContent();
655
+ };
656
+ const insertVariable = (variable) => {
657
+ if (disabled || !editorRef.current) return;
658
+ editorRef.current.focus();
659
+ const textToInsert = variable.insertValue ?? variable.value;
660
+ const selection = window.getSelection();
661
+ if (selection && selection.rangeCount > 0) {
662
+ const range = selection.getRangeAt(0);
663
+ range.deleteContents();
664
+ if (textToInsert.includes("\n")) {
665
+ const fragment = document.createDocumentFragment();
666
+ const lines = textToInsert.split("\n");
667
+ lines.forEach((line, index) => {
668
+ fragment.appendChild(document.createTextNode(line));
669
+ if (index < lines.length - 1) {
670
+ fragment.appendChild(document.createElement("br"));
671
+ }
672
+ });
673
+ range.insertNode(fragment);
674
+ range.collapse(false);
675
+ } else {
676
+ const textNode = document.createTextNode(textToInsert);
677
+ range.insertNode(textNode);
678
+ range.setStartAfter(textNode);
679
+ range.setEndAfter(textNode);
680
+ }
681
+ selection.removeAllRanges();
682
+ selection.addRange(range);
683
+ } else {
684
+ editorRef.current.innerHTML += textToInsert.replace(/\n/g, "<br>");
685
+ }
686
+ updateContent();
687
+ };
688
+ const reportError = (message) => {
689
+ if (onDictationError) onDictationError(message);
690
+ else if (typeof console !== "undefined") console.warn(message);
691
+ };
692
+ const saveSelection = () => {
693
+ const selection = window.getSelection();
694
+ if (!selection || selection.rangeCount === 0 || !editorRef.current)
695
+ return;
696
+ const range = selection.getRangeAt(0);
697
+ if (editorRef.current.contains(range.commonAncestorContainer)) {
698
+ savedRangeRef.current = range.cloneRange();
699
+ }
700
+ };
701
+ const focusRestoringSelection = () => {
702
+ if (!editorRef.current) return;
703
+ editorRef.current.focus();
704
+ const saved = savedRangeRef.current;
705
+ const selection = window.getSelection();
706
+ if (saved && selection && editorRef.current.contains(saved.commonAncestorContainer)) {
707
+ selection.removeAllRanges();
708
+ selection.addRange(saved);
709
+ }
710
+ };
711
+ const insertMacro = (macro) => {
712
+ if (disabled || !editorRef.current) return;
713
+ focusRestoringSelection();
714
+ insertTextAtSelection(macro.content);
715
+ };
716
+ const insertTextAtSelection = (content) => {
717
+ let inserted = false;
718
+ try {
719
+ inserted = document.execCommand("insertText", false, content);
720
+ } catch {
721
+ inserted = false;
722
+ }
723
+ if (!inserted) {
724
+ const fragment = document.createDocumentFragment();
725
+ const lines = content.split("\n");
726
+ lines.forEach((line, index) => {
727
+ fragment.appendChild(document.createTextNode(line));
728
+ if (index < lines.length - 1) {
729
+ fragment.appendChild(document.createElement("br"));
730
+ }
731
+ });
732
+ const sel = window.getSelection();
733
+ if (sel && sel.rangeCount > 0) {
734
+ const range = sel.getRangeAt(0);
735
+ range.deleteContents();
736
+ range.insertNode(fragment);
737
+ range.collapse(false);
738
+ sel.removeAllRanges();
739
+ sel.addRange(range);
740
+ } else if (editorRef.current) {
741
+ editorRef.current.appendChild(fragment);
742
+ }
743
+ }
744
+ saveSelection();
745
+ updateContent();
746
+ };
747
+ const expandTypedShortcut = (event) => {
748
+ if (disabled || event.key !== " " || // Space during IME composition selects/converts a candidate; never
749
+ // hijack it for macro expansion.
750
+ event.nativeEvent.isComposing || event.ctrlKey || event.metaKey || event.altKey || !macros)
751
+ return;
752
+ const selection = window.getSelection();
753
+ if (!selection || selection.rangeCount === 0 || !selection.isCollapsed)
754
+ return;
755
+ const caret = selection.getRangeAt(0);
756
+ const node = caret.startContainer;
757
+ if (node.nodeType !== Node.TEXT_NODE || !editorRef.current?.contains(node))
758
+ return;
759
+ const typed = /(\S+)$/.exec(
760
+ (node.textContent ?? "").slice(0, caret.startOffset)
761
+ );
762
+ if (!typed) return;
763
+ let token = typed[1];
764
+ let startNode = node;
765
+ let startOffset = caret.startOffset - typed[1].length;
766
+ while (startOffset === 0) {
767
+ const prev = previousVisibleText(startNode, editorRef.current);
768
+ if (!prev) break;
769
+ const text = prev.textContent ?? "";
770
+ const continued = /(\S+)$/.exec(text);
771
+ if (!continued) break;
772
+ token = continued[1] + token;
773
+ startNode = prev;
774
+ startOffset = text.length - continued[1].length;
775
+ }
776
+ const lowered = token.toLowerCase();
777
+ const macro = macros.flatMap((group) => group.macros).find((candidate) => candidate.shortcut?.toLowerCase() === lowered);
778
+ if (!macro) return;
779
+ event.preventDefault();
780
+ const replaceRange = document.createRange();
781
+ replaceRange.setStart(startNode, startOffset);
782
+ replaceRange.setEnd(node, caret.startOffset);
783
+ selection.removeAllRanges();
784
+ selection.addRange(replaceRange);
785
+ insertTextAtSelection(macro.content);
786
+ };
787
+ const toggleDictation = async () => {
788
+ const SpeechRecognition = getSpeechRecognition();
789
+ if (!SpeechRecognition) return;
790
+ if (isListening && recognitionRef.current) {
791
+ shouldRestartRef.current = false;
792
+ isListeningRef.current = false;
793
+ recognitionRef.current.stop();
794
+ setIsListening(false);
795
+ return;
796
+ }
797
+ try {
798
+ const stream = await navigator.mediaDevices.getUserMedia({
799
+ audio: true
800
+ });
801
+ stream.getTracks().forEach((track) => track.stop());
802
+ } catch {
803
+ reportError(
804
+ "Microphone access is required for dictation. Please allow microphone access in your browser settings and try again."
805
+ );
806
+ return;
807
+ }
808
+ const recognition = new SpeechRecognition();
809
+ recognition.continuous = true;
810
+ recognition.interimResults = true;
811
+ recognition.lang = "en-US";
812
+ recognition.maxAlternatives = 1;
813
+ recognition.onstart = () => {
814
+ setIsListening(true);
815
+ isListeningRef.current = true;
816
+ shouldRestartRef.current = true;
817
+ };
818
+ recognition.onresult = (event) => {
819
+ let finalTranscript = "";
820
+ for (let i = event.resultIndex; i < event.results.length; i++) {
821
+ const result = event.results[i];
822
+ if (result.isFinal) finalTranscript += result[0].transcript;
823
+ }
824
+ if (finalTranscript && editorRef.current) {
825
+ const processedText = processDictation(finalTranscript);
826
+ editorRef.current.focus();
827
+ const selection = window.getSelection();
828
+ if (selection && selection.rangeCount > 0) {
829
+ const range = selection.getRangeAt(0);
830
+ range.deleteContents();
831
+ const textNode = document.createTextNode(processedText + " ");
832
+ range.insertNode(textNode);
833
+ range.setStartAfter(textNode);
834
+ range.setEndAfter(textNode);
835
+ selection.removeAllRanges();
836
+ selection.addRange(range);
837
+ } else {
838
+ editorRef.current.innerHTML += processedText + " ";
839
+ }
840
+ updateContent();
841
+ }
842
+ };
843
+ recognition.onerror = (event) => {
844
+ if (event.error === "no-speech" || event.error === "aborted") return;
845
+ if (event.error === "not-allowed") {
846
+ reportError(
847
+ "Microphone access was denied. Please allow microphone access in your browser settings and try again."
848
+ );
849
+ shouldRestartRef.current = false;
850
+ isListeningRef.current = false;
851
+ setIsListening(false);
852
+ return;
853
+ }
854
+ if (event.error === "network") return;
855
+ shouldRestartRef.current = false;
856
+ isListeningRef.current = false;
857
+ setIsListening(false);
858
+ };
859
+ recognition.onend = () => {
860
+ if (shouldRestartRef.current && isListeningRef.current) {
861
+ setTimeout(() => {
862
+ if (shouldRestartRef.current && isListeningRef.current) {
863
+ try {
864
+ recognition.start();
865
+ } catch {
866
+ shouldRestartRef.current = false;
867
+ isListeningRef.current = false;
868
+ setIsListening(false);
869
+ }
870
+ }
871
+ }, 100);
872
+ } else {
873
+ setIsListening(false);
874
+ }
875
+ };
876
+ recognitionRef.current = recognition;
877
+ try {
878
+ recognition.start();
879
+ } catch {
880
+ reportError("Failed to start speech recognition. Please try again.");
881
+ }
882
+ };
883
+ const iconButtonClass = "h-8 w-8 p-0";
884
+ return /* @__PURE__ */ jsxRuntime.jsxs(
885
+ "div",
886
+ {
887
+ "data-slot": "rich-text-editor",
888
+ className: chunkS5RVSXDV_cjs.cn(
889
+ "border-border bg-background flex flex-col overflow-hidden rounded-lg border",
890
+ className
891
+ ),
892
+ children: [
893
+ /* @__PURE__ */ jsxRuntime.jsxs(
894
+ "div",
895
+ {
896
+ "data-slot": "rich-text-editor-toolbar",
897
+ className: "border-border bg-muted/30 sticky top-0 z-10 flex flex-wrap items-center gap-1 border-b p-2",
898
+ children: [
899
+ /* @__PURE__ */ jsxRuntime.jsx(
900
+ chunkANMX7YDL_cjs.Button,
901
+ {
902
+ type: "button",
903
+ variant: "ghost",
904
+ size: "sm",
905
+ onClick: () => execCommand("bold"),
906
+ disabled,
907
+ className: iconButtonClass,
908
+ "aria-label": "Bold",
909
+ title: "Bold",
910
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Bold, { className: "h-4 w-4" })
911
+ }
912
+ ),
913
+ /* @__PURE__ */ jsxRuntime.jsx(
914
+ chunkANMX7YDL_cjs.Button,
915
+ {
916
+ type: "button",
917
+ variant: "ghost",
918
+ size: "sm",
919
+ onClick: () => execCommand("italic"),
920
+ disabled,
921
+ className: iconButtonClass,
922
+ "aria-label": "Italic",
923
+ title: "Italic",
924
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Italic, { className: "h-4 w-4" })
925
+ }
926
+ ),
927
+ /* @__PURE__ */ jsxRuntime.jsx(
928
+ chunkANMX7YDL_cjs.Button,
929
+ {
930
+ type: "button",
931
+ variant: "ghost",
932
+ size: "sm",
933
+ onClick: () => execCommand("underline"),
934
+ disabled,
935
+ className: iconButtonClass,
936
+ "aria-label": "Underline",
937
+ title: "Underline",
938
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Underline, { className: "h-4 w-4" })
939
+ }
940
+ ),
941
+ /* @__PURE__ */ jsxRuntime.jsx(chunkYP5T6NZD_cjs.Separator, { orientation: "vertical", className: "mx-1 h-6" }),
942
+ /* @__PURE__ */ jsxRuntime.jsx(
943
+ chunkANMX7YDL_cjs.Button,
944
+ {
945
+ type: "button",
946
+ variant: "ghost",
947
+ size: "sm",
948
+ onClick: () => execCommand("insertUnorderedList"),
949
+ disabled,
950
+ className: iconButtonClass,
951
+ "aria-label": "Bullet list",
952
+ title: "Bullet list",
953
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.List, { className: "h-4 w-4" })
954
+ }
955
+ ),
956
+ /* @__PURE__ */ jsxRuntime.jsx(
957
+ chunkANMX7YDL_cjs.Button,
958
+ {
959
+ type: "button",
960
+ variant: "ghost",
961
+ size: "sm",
962
+ onClick: () => execCommand("insertOrderedList"),
963
+ disabled,
964
+ className: iconButtonClass,
965
+ "aria-label": "Numbered list",
966
+ title: "Numbered list",
967
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ListOrdered, { className: "h-4 w-4" })
968
+ }
969
+ ),
970
+ /* @__PURE__ */ jsxRuntime.jsx(chunkYP5T6NZD_cjs.Separator, { orientation: "vertical", className: "mx-1 h-6" }),
971
+ /* @__PURE__ */ jsxRuntime.jsx(
972
+ chunkANMX7YDL_cjs.Button,
973
+ {
974
+ type: "button",
975
+ variant: "ghost",
976
+ size: "sm",
977
+ onClick: () => execCommand("justifyLeft"),
978
+ disabled,
979
+ className: iconButtonClass,
980
+ "aria-label": "Align left",
981
+ title: "Align left",
982
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlignLeft, { className: "h-4 w-4" })
983
+ }
984
+ ),
985
+ /* @__PURE__ */ jsxRuntime.jsx(
986
+ chunkANMX7YDL_cjs.Button,
987
+ {
988
+ type: "button",
989
+ variant: "ghost",
990
+ size: "sm",
991
+ onClick: () => execCommand("justifyCenter"),
992
+ disabled,
993
+ className: iconButtonClass,
994
+ "aria-label": "Align center",
995
+ title: "Align center",
996
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlignCenter, { className: "h-4 w-4" })
997
+ }
998
+ ),
999
+ /* @__PURE__ */ jsxRuntime.jsx(
1000
+ chunkANMX7YDL_cjs.Button,
1001
+ {
1002
+ type: "button",
1003
+ variant: "ghost",
1004
+ size: "sm",
1005
+ onClick: () => execCommand("justifyRight"),
1006
+ disabled,
1007
+ className: iconButtonClass,
1008
+ "aria-label": "Align right",
1009
+ title: "Align right",
1010
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlignRight, { className: "h-4 w-4" })
1011
+ }
1012
+ ),
1013
+ hasVariables && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1014
+ /* @__PURE__ */ jsxRuntime.jsx(chunkYP5T6NZD_cjs.Separator, { orientation: "vertical", className: "mx-1 h-6" }),
1015
+ /* @__PURE__ */ jsxRuntime.jsx(
1016
+ chunkPINIBEIU_cjs.Dropdown,
1017
+ {
1018
+ placement: "bottom-start",
1019
+ width: 240,
1020
+ searchable: true,
1021
+ searchPlaceholder: "Search variables\u2026",
1022
+ searchAriaLabel: "Search template variables",
1023
+ disabled,
1024
+ trigger: /* @__PURE__ */ jsxRuntime.jsxs(
1025
+ chunkANMX7YDL_cjs.Button,
1026
+ {
1027
+ type: "button",
1028
+ variant: "ghost",
1029
+ size: "sm",
1030
+ className: "h-8 gap-1 px-2",
1031
+ disabled,
1032
+ title: "Insert variable",
1033
+ children: [
1034
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Braces, { className: "h-4 w-4" }),
1035
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ms-1 text-xs", children: "Variables" })
1036
+ ]
1037
+ }
1038
+ ),
1039
+ children: variableGroups.map((group, groupIndex) => /* @__PURE__ */ jsxRuntime.jsxs(React2__namespace.Fragment, { children: [
1040
+ groupIndex > 0 && /* @__PURE__ */ jsxRuntime.jsx(chunkPINIBEIU_cjs.DropdownSeparator, {}),
1041
+ /* @__PURE__ */ jsxRuntime.jsx(chunkPINIBEIU_cjs.DropdownLabel, { children: group.label }),
1042
+ group.variables.map((variable) => /* @__PURE__ */ jsxRuntime.jsx(
1043
+ chunkPINIBEIU_cjs.DropdownItem,
1044
+ {
1045
+ onClick: () => insertVariable(variable),
1046
+ children: variable.label
1047
+ },
1048
+ variable.value
1049
+ ))
1050
+ ] }, group.label))
1051
+ }
1052
+ ),
1053
+ /* @__PURE__ */ jsxRuntime.jsx(chunkYP5T6NZD_cjs.Separator, { orientation: "vertical", className: "mx-1 h-6" }),
1054
+ /* @__PURE__ */ jsxRuntime.jsxs(
1055
+ chunkANMX7YDL_cjs.Button,
1056
+ {
1057
+ type: "button",
1058
+ variant: "ghost",
1059
+ size: "sm",
1060
+ onClick: handleConvertToMustache,
1061
+ disabled,
1062
+ className: "h-8 gap-1 px-2",
1063
+ title: "Convert <<field>> to {{field}}",
1064
+ children: [
1065
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Replace, { className: "h-4 w-4" }),
1066
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ms-1 text-xs", children: "<< >> to {{ }}" })
1067
+ ]
1068
+ }
1069
+ )
1070
+ ] }),
1071
+ enableDictation && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1072
+ /* @__PURE__ */ jsxRuntime.jsx(chunkYP5T6NZD_cjs.Separator, { orientation: "vertical", className: "mx-1 h-6" }),
1073
+ /* @__PURE__ */ jsxRuntime.jsxs(
1074
+ chunkANMX7YDL_cjs.Button,
1075
+ {
1076
+ type: "button",
1077
+ variant: isListening ? "danger" : "ghost",
1078
+ size: "sm",
1079
+ onClick: toggleDictation,
1080
+ disabled: disabled || !speechSupported,
1081
+ className: chunkS5RVSXDV_cjs.cn("h-8 gap-1 px-2", isListening && "animate-pulse"),
1082
+ title: !speechSupported ? "Speech recognition not supported in this browser" : isListening ? "Stop dictation" : "Start dictation",
1083
+ children: [
1084
+ isListening ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MicOff, { className: "h-4 w-4" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Mic, { className: "h-4 w-4" }),
1085
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ms-1 text-xs", children: isListening ? "Stop" : "Dictate" })
1086
+ ]
1087
+ }
1088
+ )
1089
+ ] }),
1090
+ hasMacros && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1091
+ /* @__PURE__ */ jsxRuntime.jsx(chunkYP5T6NZD_cjs.Separator, { orientation: "vertical", className: "mx-1 h-6" }),
1092
+ /* @__PURE__ */ jsxRuntime.jsxs(
1093
+ chunkANMX7YDL_cjs.Button,
1094
+ {
1095
+ type: "button",
1096
+ variant: "ghost",
1097
+ size: "sm",
1098
+ onClick: () => setMacrosOpen((open) => !open),
1099
+ disabled,
1100
+ className: chunkS5RVSXDV_cjs.cn("h-8 gap-1 px-2", macrosOpen && "bg-muted"),
1101
+ "aria-expanded": macrosOpen,
1102
+ "aria-controls": macrosOpen ? macroPanelId : void 0,
1103
+ title: macrosOpen ? macroLabels?.hideMacros ?? "Hide macros" : macroLabels?.showMacros ?? "Show macros",
1104
+ children: [
1105
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.BookOpen, { className: "h-4 w-4" }),
1106
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ms-1 text-xs", children: macroLabels?.macros ?? "Macros" })
1107
+ ]
1108
+ }
1109
+ )
1110
+ ] })
1111
+ ]
1112
+ }
1113
+ ),
1114
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex min-h-0 flex-1 flex-col", children: [
1115
+ /* @__PURE__ */ jsxRuntime.jsxs(
1116
+ "div",
1117
+ {
1118
+ className: chunkS5RVSXDV_cjs.cn(
1119
+ "relative flex-1",
1120
+ hasMacros && macrosOpen && "md:me-72"
1121
+ ),
1122
+ children: [
1123
+ isEmpty && placeholder && /* @__PURE__ */ jsxRuntime.jsx(
1124
+ "div",
1125
+ {
1126
+ "aria-hidden": "true",
1127
+ className: "text-muted-foreground pointer-events-none absolute start-0 top-0 p-4",
1128
+ children: placeholder
1129
+ }
1130
+ ),
1131
+ /* @__PURE__ */ jsxRuntime.jsx(
1132
+ "div",
1133
+ {
1134
+ ref: editorRef,
1135
+ role: "textbox",
1136
+ "aria-multiline": "true",
1137
+ "aria-label": ariaLabel ?? placeholder,
1138
+ contentEditable: !disabled,
1139
+ tabIndex: disabled ? -1 : 0,
1140
+ onInput: updateContent,
1141
+ onPaste: handlePaste,
1142
+ onDrop: handleDrop,
1143
+ onBlur: hasMacros ? saveSelection : void 0,
1144
+ onKeyDown: hasMacros ? expandTypedShortcut : void 0,
1145
+ onKeyUp: hasMacros ? saveSelection : void 0,
1146
+ onMouseUp: hasMacros ? saveSelection : void 0,
1147
+ className: chunkS5RVSXDV_cjs.cn(
1148
+ "min-h-[250px] overflow-y-auto p-4 focus:outline-none",
1149
+ "prose prose-sm max-w-none",
1150
+ hasMacros && macrosOpen && "md:min-h-[400px]",
1151
+ disabled && "bg-muted/20 cursor-not-allowed text-neutral-700 dark:text-neutral-300"
1152
+ ),
1153
+ suppressContentEditableWarning: true
1154
+ }
1155
+ )
1156
+ ]
1157
+ }
1158
+ ),
1159
+ hasMacros && macrosOpen && /* @__PURE__ */ jsxRuntime.jsx(
1160
+ MacroLibrary,
1161
+ {
1162
+ id: macroPanelId,
1163
+ groups: macros,
1164
+ onSelect: insertMacro,
1165
+ targetLabel: ariaLabel ?? placeholder,
1166
+ onClose: () => {
1167
+ setMacrosOpen(false);
1168
+ focusRestoringSelection();
1169
+ },
1170
+ onCreateMacro,
1171
+ labels: macroLabels,
1172
+ className: "border-border max-h-72 border-t md:absolute md:inset-y-0 md:end-0 md:max-h-none md:w-72 md:border-s md:border-t-0"
1173
+ }
1174
+ )
1175
+ ] })
1176
+ ]
1177
+ }
1178
+ );
1179
+ }
1180
+ );
1181
+ RichTextEditor.displayName = "RichTextEditor";
1182
+
1183
+ exports.MacroLibrary = MacroLibrary;
1184
+ exports.RichTextEditor = RichTextEditor;
1185
+ exports.convertAngleBracketsToMustache = convertAngleBracketsToMustache;
1186
+ exports.processDictation = processDictation;
1187
+ //# sourceMappingURL=chunk-7BA7ENN5.cjs.map
1188
+ //# sourceMappingURL=chunk-7BA7ENN5.cjs.map