@particle-academy/react-fancy 5.38.0 → 5.39.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.
- package/CHANGELOG.md +57 -0
- package/dist/{index-CazpqSsk.d.cts → Composer.types-CuSPnaOt.d.cts} +1 -4
- package/dist/{index-CazpqSsk.d.ts → Composer.types-CuSPnaOt.d.ts} +1 -4
- package/dist/{chunk-Z4GO6N6N.js → chunk-2JOWGYAJ.js} +322 -408
- package/dist/chunk-2JOWGYAJ.js.map +1 -0
- package/dist/{chunk-RYKPYILS.cjs → chunk-6TZPM43Q.cjs} +325 -408
- package/dist/chunk-6TZPM43Q.cjs.map +1 -0
- package/dist/chunk-BZOU7VJQ.cjs +231 -0
- package/dist/chunk-BZOU7VJQ.cjs.map +1 -0
- package/dist/{chunk-QLL7MWAU.cjs → chunk-FJ2GMQLC.cjs} +116 -26
- package/dist/chunk-FJ2GMQLC.cjs.map +1 -0
- package/dist/chunk-MXAVGOHW.js +229 -0
- package/dist/chunk-MXAVGOHW.js.map +1 -0
- package/dist/{chunk-WUV3XDJH.js → chunk-YLPVTEUH.js} +117 -27
- package/dist/chunk-YLPVTEUH.js.map +1 -0
- package/dist/composer.cjs +4 -3
- package/dist/composer.d.cts +7 -2
- package/dist/composer.d.ts +7 -2
- package/dist/composer.js +2 -1
- package/dist/{index-DXUAAZzE.d.ts → index-BsIBOwaK.d.cts} +46 -1
- package/dist/{index-DXUAAZzE.d.cts → index-S4FC1yFK.d.ts} +46 -1
- package/dist/index.cjs +10 -9
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/prompt-input.cjs +4 -2
- package/dist/prompt-input.d.cts +2 -1
- package/dist/prompt-input.d.ts +2 -1
- package/dist/prompt-input.js +3 -1
- package/package.json +1 -1
- package/dist/chunk-QLL7MWAU.cjs.map +0 -1
- package/dist/chunk-RYKPYILS.cjs.map +0 -1
- package/dist/chunk-WUV3XDJH.js.map +0 -1
- package/dist/chunk-Z4GO6N6N.js.map +0 -1
|
@@ -2,9 +2,37 @@ import { Tooltip } from './chunk-CF5KRDN4.js';
|
|
|
2
2
|
import { useControllableState } from './chunk-EDYAXGUM.js';
|
|
3
3
|
import { cn } from './chunk-MMW2JGKZ.js';
|
|
4
4
|
import { forwardRef, useRef, useCallback, useLayoutEffect, useImperativeHandle, useMemo, useState, useEffect } from 'react';
|
|
5
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
5
6
|
import { createPortal } from 'react-dom';
|
|
6
|
-
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
7
7
|
|
|
8
|
+
// src/components/Composer/paste-markers.ts
|
|
9
|
+
var PILL_MARK = "\uFFFC";
|
|
10
|
+
var MARKER = /\uFFFC([^\uFFFC\n]+)\uFFFC/g;
|
|
11
|
+
function pasteMarker(id) {
|
|
12
|
+
return `${PILL_MARK}${id}${PILL_MARK}`;
|
|
13
|
+
}
|
|
14
|
+
function segmentValue(value, known) {
|
|
15
|
+
const out = [];
|
|
16
|
+
let last = 0;
|
|
17
|
+
for (const match of value.matchAll(MARKER)) {
|
|
18
|
+
const id = match[1];
|
|
19
|
+
if (!known.has(id)) continue;
|
|
20
|
+
const start = match.index ?? 0;
|
|
21
|
+
if (start > last) out.push({ type: "text", text: value.slice(last, start) });
|
|
22
|
+
out.push({ type: "pill", id });
|
|
23
|
+
last = start + match[0].length;
|
|
24
|
+
}
|
|
25
|
+
if (last < value.length) out.push({ type: "text", text: value.slice(last) });
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
function markedIds(value) {
|
|
29
|
+
return Array.from(value.matchAll(MARKER), (m) => m[1]);
|
|
30
|
+
}
|
|
31
|
+
function expandMarkers(value, pastes, images) {
|
|
32
|
+
const text = new Map(pastes.map((p) => [p.id, p.text]));
|
|
33
|
+
const image = new Map(images.map((i) => [i.id, `[${i.label}]`]));
|
|
34
|
+
return value.replace(MARKER, (whole, id) => text.get(id) ?? image.get(id) ?? whole);
|
|
35
|
+
}
|
|
8
36
|
function usePastePills({
|
|
9
37
|
threshold,
|
|
10
38
|
heldPastes: controlledHeldPastes,
|
|
@@ -55,34 +83,67 @@ function usePastePills({
|
|
|
55
83
|
const clear = useCallback(() => setHeldPastes([]), [setHeldPastes]);
|
|
56
84
|
return { heldPastes, handlePaste, hold, expand, remove, clear };
|
|
57
85
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
var PILL_MARK = "\uFFFC";
|
|
61
|
-
var MARKER = /\uFFFC([^\uFFFC\n]+)\uFFFC/g;
|
|
62
|
-
function pasteMarker(id) {
|
|
63
|
-
return `${PILL_MARK}${id}${PILL_MARK}`;
|
|
86
|
+
function withSeparators(n) {
|
|
87
|
+
return String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
64
88
|
}
|
|
65
|
-
function
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
for (const match of value.matchAll(MARKER)) {
|
|
69
|
-
const id = match[1];
|
|
70
|
-
if (!known.has(id)) continue;
|
|
71
|
-
const start = match.index ?? 0;
|
|
72
|
-
if (start > last) out.push({ type: "text", text: value.slice(last, start) });
|
|
73
|
-
out.push({ type: "pill", id });
|
|
74
|
-
last = start + match[0].length;
|
|
75
|
-
}
|
|
76
|
-
if (last < value.length) out.push({ type: "text", text: value.slice(last) });
|
|
77
|
-
return out;
|
|
89
|
+
function endsOf(text, chars) {
|
|
90
|
+
if (text.length <= chars * 2) return text;
|
|
91
|
+
return `${text.slice(0, chars)} \u2026 ${text.slice(-chars)}`;
|
|
78
92
|
}
|
|
79
|
-
|
|
80
|
-
|
|
93
|
+
var PILL_BASE = "inline-flex select-none items-center rounded-md px-2 py-0.5 text-xs leading-5 transition-colors";
|
|
94
|
+
var TEXT_PILL = "cursor-pointer bg-zinc-100 text-zinc-700 hover:bg-zinc-200 dark:bg-zinc-800 dark:text-zinc-200 dark:hover:bg-zinc-700";
|
|
95
|
+
var IMAGE_PILL = "bg-rose-100 font-medium text-rose-600 dark:bg-rose-500/20 dark:text-rose-300";
|
|
96
|
+
var PREVIEW_BASE = "max-w-xs whitespace-pre-wrap break-all text-xs";
|
|
97
|
+
function ImageThumb({ file }) {
|
|
98
|
+
const [url, setUrl] = useState(null);
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
if (typeof URL.createObjectURL !== "function") return;
|
|
101
|
+
const made = URL.createObjectURL(file);
|
|
102
|
+
setUrl(made);
|
|
103
|
+
return () => URL.revokeObjectURL(made);
|
|
104
|
+
}, [file]);
|
|
105
|
+
return url ? /* @__PURE__ */ jsx("img", { src: url, alt: "", className: "max-h-40 max-w-[16rem] rounded" }) : /* @__PURE__ */ jsx("span", { children: file.name });
|
|
81
106
|
}
|
|
82
|
-
function
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
107
|
+
function InlinePill({
|
|
108
|
+
preview,
|
|
109
|
+
previewChars,
|
|
110
|
+
renderPastePreview,
|
|
111
|
+
classNames,
|
|
112
|
+
disabled,
|
|
113
|
+
onExpand
|
|
114
|
+
}) {
|
|
115
|
+
const id = preview.kind === "text" ? preview.paste.id : preview.image.id;
|
|
116
|
+
const content = /* @__PURE__ */ jsx("span", { "data-react-fancy-composer-paste-pill-preview": id, children: renderPastePreview?.(preview) ?? (preview.kind === "text" ? endsOf(preview.paste.text, previewChars) : /* @__PURE__ */ jsx(ImageThumb, { file: preview.image.file })) });
|
|
117
|
+
const tooltipClass = cn(PREVIEW_BASE, classNames?.preview);
|
|
118
|
+
if (preview.kind === "image") {
|
|
119
|
+
return /* @__PURE__ */ jsx(Tooltip, { content, className: tooltipClass, children: /* @__PURE__ */ jsx(
|
|
120
|
+
"span",
|
|
121
|
+
{
|
|
122
|
+
"data-react-fancy-composer-image-pill": id,
|
|
123
|
+
role: "img",
|
|
124
|
+
"aria-label": preview.image.label,
|
|
125
|
+
className: cn(PILL_BASE, IMAGE_PILL, classNames?.pill, classNames?.imagePill),
|
|
126
|
+
children: preview.image.label
|
|
127
|
+
}
|
|
128
|
+
) });
|
|
129
|
+
}
|
|
130
|
+
const count = withSeparators(preview.paste.text.length);
|
|
131
|
+
return /* @__PURE__ */ jsx(Tooltip, { content, className: tooltipClass, children: /* @__PURE__ */ jsxs(
|
|
132
|
+
"button",
|
|
133
|
+
{
|
|
134
|
+
type: "button",
|
|
135
|
+
"data-react-fancy-composer-paste-pill": id,
|
|
136
|
+
onClick: () => onExpand(id),
|
|
137
|
+
disabled,
|
|
138
|
+
"aria-label": `Insert this pasted text, ${count} characters`,
|
|
139
|
+
className: cn(PILL_BASE, TEXT_PILL, classNames?.pill, classNames?.textPill),
|
|
140
|
+
children: [
|
|
141
|
+
"Pasted text \xB7 ",
|
|
142
|
+
count,
|
|
143
|
+
" characters"
|
|
144
|
+
]
|
|
145
|
+
}
|
|
146
|
+
) });
|
|
86
147
|
}
|
|
87
148
|
var HOST_ATTR = "data-react-fancy-composer-paste-host";
|
|
88
149
|
function serialize(parent, keep) {
|
|
@@ -132,8 +193,60 @@ function placeCaretAfter(node) {
|
|
|
132
193
|
selection.removeAllRanges();
|
|
133
194
|
selection.addRange(range);
|
|
134
195
|
}
|
|
196
|
+
function placeCaretAtOffset(root, offset, keep) {
|
|
197
|
+
const selection = window.getSelection();
|
|
198
|
+
if (!selection) return;
|
|
199
|
+
const range = document.createRange();
|
|
200
|
+
let remaining = offset;
|
|
201
|
+
let placed = false;
|
|
202
|
+
for (const node of Array.from(root.childNodes)) {
|
|
203
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
204
|
+
const length = node.textContent?.length ?? 0;
|
|
205
|
+
if (remaining <= length) {
|
|
206
|
+
range.setStart(node, remaining);
|
|
207
|
+
placed = true;
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
remaining -= length;
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
const id = node instanceof HTMLElement ? node.getAttribute(HOST_ATTR) : null;
|
|
214
|
+
if (id !== null && keep(id)) {
|
|
215
|
+
const length = pasteMarker(id).length;
|
|
216
|
+
if (remaining < length) {
|
|
217
|
+
range.setStartAfter(node);
|
|
218
|
+
placed = true;
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
remaining -= length;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (!placed) {
|
|
225
|
+
range.selectNodeContents(root);
|
|
226
|
+
range.collapse(false);
|
|
227
|
+
const last = root.lastChild;
|
|
228
|
+
if (last instanceof HTMLElement && last.tagName === "BR") range.setStartBefore(last);
|
|
229
|
+
}
|
|
230
|
+
range.collapse(true);
|
|
231
|
+
selection.removeAllRanges();
|
|
232
|
+
selection.addRange(range);
|
|
233
|
+
}
|
|
135
234
|
var InlinePasteEditor = forwardRef(
|
|
136
|
-
function InlinePasteEditor2({
|
|
235
|
+
function InlinePasteEditor2({
|
|
236
|
+
value,
|
|
237
|
+
known,
|
|
238
|
+
onChange,
|
|
239
|
+
onSubmit,
|
|
240
|
+
onPaste,
|
|
241
|
+
onHostsChange,
|
|
242
|
+
placeholder,
|
|
243
|
+
disabled,
|
|
244
|
+
className,
|
|
245
|
+
submitKey = "enter",
|
|
246
|
+
onKeyDown,
|
|
247
|
+
placeholderClassName,
|
|
248
|
+
maxHeight
|
|
249
|
+
}, ref) {
|
|
137
250
|
const rootRef = useRef(null);
|
|
138
251
|
const lastValue = useRef(null);
|
|
139
252
|
const pending = useRef(/* @__PURE__ */ new Set());
|
|
@@ -153,17 +266,47 @@ var InlinePasteEditor = forwardRef(
|
|
|
153
266
|
});
|
|
154
267
|
onHostsChange(hosts);
|
|
155
268
|
}, [keep, onHostsChange]);
|
|
269
|
+
const caretOffset = useCallback(
|
|
270
|
+
(whole) => {
|
|
271
|
+
const root = rootRef.current;
|
|
272
|
+
const selection = window.getSelection();
|
|
273
|
+
if (!root || !selection || selection.rangeCount === 0) return whole.length;
|
|
274
|
+
const range = selection.getRangeAt(0);
|
|
275
|
+
if (!root.contains(range.endContainer)) return whole.length;
|
|
276
|
+
const head = document.createRange();
|
|
277
|
+
head.selectNodeContents(root);
|
|
278
|
+
head.setEnd(range.endContainer, range.endOffset);
|
|
279
|
+
return serialize(head.cloneContents(), keep).length;
|
|
280
|
+
},
|
|
281
|
+
[keep]
|
|
282
|
+
);
|
|
156
283
|
const emit = useCallback(() => {
|
|
157
284
|
const root = rootRef.current;
|
|
158
285
|
if (!root) return;
|
|
159
286
|
const next = serialize(root, keep);
|
|
160
287
|
ensureTrailingBreak(root, next);
|
|
161
288
|
reportHosts();
|
|
162
|
-
|
|
289
|
+
lastValue.current = next;
|
|
290
|
+
onChange(next, caretOffset(next));
|
|
291
|
+
}, [keep, onChange, reportHosts, caretOffset]);
|
|
292
|
+
const render = useCallback(
|
|
293
|
+
(next) => {
|
|
294
|
+
const root = rootRef.current;
|
|
295
|
+
if (!root) return;
|
|
296
|
+
root.replaceChildren();
|
|
297
|
+
for (const segment of segmentValue(next, knownRef.current)) {
|
|
298
|
+
if (segment.type === "text") {
|
|
299
|
+
root.append(document.createTextNode(segment.text));
|
|
300
|
+
} else {
|
|
301
|
+
root.append(makeHost(segment.id));
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
ensureTrailingBreak(root, next);
|
|
163
305
|
lastValue.current = next;
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
|
|
306
|
+
reportHosts();
|
|
307
|
+
},
|
|
308
|
+
[reportHosts]
|
|
309
|
+
);
|
|
167
310
|
const caret = useCallback(() => {
|
|
168
311
|
const root = rootRef.current;
|
|
169
312
|
const selection = window.getSelection();
|
|
@@ -216,18 +359,8 @@ var InlinePasteEditor = forwardRef(
|
|
|
216
359
|
const wanted = segmentValue(value, known).flatMap((s) => s.type === "pill" ? [s.id] : []).join(PILL_MARK);
|
|
217
360
|
const onScreen = Array.from(root.querySelectorAll(`[${HOST_ATTR}]`)).map((el) => el.getAttribute(HOST_ATTR)).filter((id) => id !== null && keep(id)).join(PILL_MARK);
|
|
218
361
|
if (value === lastValue.current && wanted === onScreen) return;
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
if (segment.type === "text") {
|
|
222
|
-
root.append(document.createTextNode(segment.text));
|
|
223
|
-
} else {
|
|
224
|
-
root.append(makeHost(segment.id));
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
ensureTrailingBreak(root, value);
|
|
228
|
-
lastValue.current = value;
|
|
229
|
-
reportHosts();
|
|
230
|
-
}, [value, known, keep, reportHosts]);
|
|
362
|
+
render(value);
|
|
363
|
+
}, [value, known, keep, render]);
|
|
231
364
|
useImperativeHandle(
|
|
232
365
|
ref,
|
|
233
366
|
() => ({
|
|
@@ -251,9 +384,17 @@ var InlinePasteEditor = forwardRef(
|
|
|
251
384
|
root.focus();
|
|
252
385
|
placeCaretAfter(node);
|
|
253
386
|
emit();
|
|
387
|
+
},
|
|
388
|
+
setValue(next, offset) {
|
|
389
|
+
const root = rootRef.current;
|
|
390
|
+
if (!root) return;
|
|
391
|
+
render(next);
|
|
392
|
+
root.focus();
|
|
393
|
+
placeCaretAtOffset(root, offset, keep);
|
|
394
|
+
emit();
|
|
254
395
|
}
|
|
255
396
|
}),
|
|
256
|
-
[caret, emit]
|
|
397
|
+
[caret, emit, render, keep]
|
|
257
398
|
);
|
|
258
399
|
const handlePaste = (e) => {
|
|
259
400
|
e.preventDefault();
|
|
@@ -282,12 +423,15 @@ var InlinePasteEditor = forwardRef(
|
|
|
282
423
|
});
|
|
283
424
|
};
|
|
284
425
|
const handleKeyDown = (e) => {
|
|
426
|
+
onKeyDown?.(e);
|
|
427
|
+
if (e.defaultPrevented) return;
|
|
285
428
|
if (e.key !== "Enter" || e.nativeEvent.isComposing) return;
|
|
286
429
|
e.preventDefault();
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
} else {
|
|
430
|
+
const sends = submitKey === "mod-enter" ? e.ctrlKey || e.metaKey : !e.shiftKey;
|
|
431
|
+
if (sends) {
|
|
290
432
|
onSubmit();
|
|
433
|
+
} else {
|
|
434
|
+
insertText("\n");
|
|
291
435
|
}
|
|
292
436
|
};
|
|
293
437
|
return /* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
@@ -295,7 +439,10 @@ var InlinePasteEditor = forwardRef(
|
|
|
295
439
|
"span",
|
|
296
440
|
{
|
|
297
441
|
"aria-hidden": "true",
|
|
298
|
-
className:
|
|
442
|
+
className: cn(
|
|
443
|
+
"pointer-events-none absolute left-4 top-3 text-sm text-zinc-400",
|
|
444
|
+
placeholderClassName
|
|
445
|
+
),
|
|
299
446
|
children: placeholder
|
|
300
447
|
}
|
|
301
448
|
),
|
|
@@ -313,6 +460,7 @@ var InlinePasteEditor = forwardRef(
|
|
|
313
460
|
onInput: emit,
|
|
314
461
|
onPaste: handlePaste,
|
|
315
462
|
onKeyDown: handleKeyDown,
|
|
463
|
+
style: maxHeight === void 0 ? void 0 : { maxHeight },
|
|
316
464
|
className: cn(
|
|
317
465
|
"max-h-72 min-h-[4.5rem] w-full overflow-y-auto whitespace-pre-wrap break-words bg-transparent px-4 pt-3 text-sm outline-none",
|
|
318
466
|
className
|
|
@@ -322,374 +470,140 @@ var InlinePasteEditor = forwardRef(
|
|
|
322
470
|
] });
|
|
323
471
|
}
|
|
324
472
|
);
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
}
|
|
329
|
-
function endsOf(text, chars) {
|
|
330
|
-
if (text.length <= chars * 2) return text;
|
|
331
|
-
return `${text.slice(0, chars)} \u2026 ${text.slice(-chars)}`;
|
|
332
|
-
}
|
|
333
|
-
var PILL_BASE = "inline-flex select-none items-center rounded-md px-2 py-0.5 text-xs leading-5 transition-colors";
|
|
334
|
-
var TEXT_PILL = "cursor-pointer bg-zinc-100 text-zinc-700 hover:bg-zinc-200 dark:bg-zinc-800 dark:text-zinc-200 dark:hover:bg-zinc-700";
|
|
335
|
-
var IMAGE_PILL = "bg-rose-100 font-medium text-rose-600 dark:bg-rose-500/20 dark:text-rose-300";
|
|
336
|
-
var PREVIEW_BASE = "max-w-xs whitespace-pre-wrap break-all text-xs";
|
|
337
|
-
function ImageThumb({ file }) {
|
|
338
|
-
const [url, setUrl] = useState(null);
|
|
339
|
-
useEffect(() => {
|
|
340
|
-
if (typeof URL.createObjectURL !== "function") return;
|
|
341
|
-
const made = URL.createObjectURL(file);
|
|
342
|
-
setUrl(made);
|
|
343
|
-
return () => URL.revokeObjectURL(made);
|
|
344
|
-
}, [file]);
|
|
345
|
-
return url ? /* @__PURE__ */ jsx("img", { src: url, alt: "", className: "max-h-40 max-w-[16rem] rounded" }) : /* @__PURE__ */ jsx("span", { children: file.name });
|
|
346
|
-
}
|
|
347
|
-
function InlinePill({
|
|
348
|
-
preview,
|
|
473
|
+
function useInlinePaste({
|
|
474
|
+
pasteThreshold,
|
|
475
|
+
pasteImages = false,
|
|
349
476
|
previewChars,
|
|
350
477
|
renderPastePreview,
|
|
351
478
|
classNames,
|
|
352
479
|
disabled,
|
|
353
|
-
|
|
480
|
+
heldPastes: controlledHeldPastes,
|
|
481
|
+
onHeldPastesChange,
|
|
482
|
+
heldImages: controlledHeldImages,
|
|
483
|
+
onHeldImagesChange,
|
|
484
|
+
editorRef
|
|
354
485
|
}) {
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
const tooltipClass = cn(PREVIEW_BASE, classNames?.preview);
|
|
358
|
-
if (preview.kind === "image") {
|
|
359
|
-
return /* @__PURE__ */ jsx(Tooltip, { content, className: tooltipClass, children: /* @__PURE__ */ jsx(
|
|
360
|
-
"span",
|
|
361
|
-
{
|
|
362
|
-
"data-react-fancy-composer-image-pill": id,
|
|
363
|
-
role: "img",
|
|
364
|
-
"aria-label": preview.image.label,
|
|
365
|
-
className: cn(PILL_BASE, IMAGE_PILL, classNames?.pill, classNames?.imagePill),
|
|
366
|
-
children: preview.image.label
|
|
367
|
-
}
|
|
368
|
-
) });
|
|
369
|
-
}
|
|
370
|
-
const count = withSeparators(preview.paste.text.length);
|
|
371
|
-
return /* @__PURE__ */ jsx(Tooltip, { content, className: tooltipClass, children: /* @__PURE__ */ jsxs(
|
|
372
|
-
"button",
|
|
373
|
-
{
|
|
374
|
-
type: "button",
|
|
375
|
-
"data-react-fancy-composer-paste-pill": id,
|
|
376
|
-
onClick: () => onExpand(id),
|
|
377
|
-
disabled,
|
|
378
|
-
"aria-label": `Insert this pasted text, ${count} characters`,
|
|
379
|
-
className: cn(PILL_BASE, TEXT_PILL, classNames?.pill, classNames?.textPill),
|
|
380
|
-
children: [
|
|
381
|
-
"Pasted text \xB7 ",
|
|
382
|
-
count,
|
|
383
|
-
" characters"
|
|
384
|
-
]
|
|
385
|
-
}
|
|
386
|
-
) });
|
|
387
|
-
}
|
|
388
|
-
var Composer = forwardRef(
|
|
389
|
-
function Composer2({
|
|
390
|
-
value: controlledValue,
|
|
391
|
-
defaultValue = "",
|
|
392
|
-
onChange,
|
|
393
|
-
onSubmit,
|
|
394
|
-
placeholder = "Type a message...",
|
|
395
|
-
actions,
|
|
396
|
-
disabled = false,
|
|
397
|
-
className,
|
|
398
|
-
pasteThreshold,
|
|
399
|
-
pastePlacement = "above",
|
|
400
|
-
pasteImages = false,
|
|
401
|
-
previewChars = PREVIEW_CHARS,
|
|
402
|
-
renderPastePreview,
|
|
403
|
-
classNames,
|
|
486
|
+
const pills = usePastePills({
|
|
487
|
+
threshold: pasteThreshold,
|
|
404
488
|
heldPastes: controlledHeldPastes,
|
|
405
|
-
onHeldPastesChange
|
|
406
|
-
|
|
489
|
+
onHeldPastesChange
|
|
490
|
+
});
|
|
491
|
+
const heldPastes = pills.heldPastes;
|
|
492
|
+
const [heldImages, setHeldImages] = useControllableState(
|
|
493
|
+
controlledHeldImages,
|
|
494
|
+
[],
|
|
407
495
|
onHeldImagesChange
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
(
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
},
|
|
437
|
-
[pills]
|
|
438
|
-
);
|
|
439
|
-
const holdImage = useCallback(
|
|
440
|
-
(file) => {
|
|
441
|
-
let id;
|
|
442
|
-
do {
|
|
443
|
-
imageCount.current += 1;
|
|
444
|
-
id = `image-${imageCount.current}`;
|
|
445
|
-
} while (heldImages.some((i) => i.id === id));
|
|
446
|
-
const image = { id, file, label: `Pasted image ${imageCount.current}` };
|
|
447
|
-
setHeldImages((current) => [...current, image]);
|
|
448
|
-
return id;
|
|
449
|
-
},
|
|
450
|
-
[heldImages, setHeldImages]
|
|
451
|
-
);
|
|
452
|
-
const decidePaste = useCallback(
|
|
453
|
-
(data) => {
|
|
454
|
-
const text = data?.getData("text/plain") ?? "";
|
|
455
|
-
if (pasteImages && text === "") {
|
|
456
|
-
const files = Array.from(data?.files ?? []).filter(
|
|
457
|
-
(f) => f.type.startsWith("image/")
|
|
458
|
-
);
|
|
459
|
-
if (files.length > 0) {
|
|
460
|
-
return files.map((file) => ({ id: holdImage(file), block: false }));
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
if (pasteThreshold && pasteThreshold > 0 && text.length >= pasteThreshold) {
|
|
464
|
-
return [{ id: pills.hold(text), block: true }];
|
|
465
|
-
}
|
|
466
|
-
return null;
|
|
467
|
-
},
|
|
468
|
-
[pasteImages, pasteThreshold, holdImage, pills]
|
|
469
|
-
);
|
|
470
|
-
const handleInlineChange = useCallback(
|
|
471
|
-
(next) => {
|
|
472
|
-
setValue(next);
|
|
473
|
-
const present = new Set(markedIds(next));
|
|
474
|
-
for (const paste of heldPastes) if (!present.has(paste.id)) pills.remove(paste.id);
|
|
475
|
-
if (heldImages.some((i) => !present.has(i.id))) {
|
|
476
|
-
setHeldImages((current) => current.filter((i) => present.has(i.id)));
|
|
477
|
-
}
|
|
478
|
-
},
|
|
479
|
-
[setValue, heldPastes, heldImages, pills, setHeldImages]
|
|
480
|
-
);
|
|
481
|
-
const [hosts, setHosts] = useState(/* @__PURE__ */ new Map());
|
|
482
|
-
const handleHostsChange = useCallback((next) => {
|
|
483
|
-
setHosts((prev) => {
|
|
484
|
-
if (prev.size === next.size && [...next].every(([id, el]) => prev.get(id) === el)) {
|
|
485
|
-
return prev;
|
|
496
|
+
);
|
|
497
|
+
const imageCount = useRef(0);
|
|
498
|
+
const known = useMemo(
|
|
499
|
+
() => /* @__PURE__ */ new Set([...heldPastes.map((p) => p.id), ...heldImages.map((i) => i.id)]),
|
|
500
|
+
[heldPastes, heldImages]
|
|
501
|
+
);
|
|
502
|
+
const holdImage = useCallback(
|
|
503
|
+
(file) => {
|
|
504
|
+
let id;
|
|
505
|
+
do {
|
|
506
|
+
imageCount.current += 1;
|
|
507
|
+
id = `image-${imageCount.current}`;
|
|
508
|
+
} while (heldImages.some((i) => i.id === id));
|
|
509
|
+
const image = { id, file, label: `Pasted image ${imageCount.current}` };
|
|
510
|
+
setHeldImages((current) => [...current, image]);
|
|
511
|
+
return id;
|
|
512
|
+
},
|
|
513
|
+
[heldImages, setHeldImages]
|
|
514
|
+
);
|
|
515
|
+
const decidePaste = useCallback(
|
|
516
|
+
(data) => {
|
|
517
|
+
const text = data?.getData("text/plain") ?? "";
|
|
518
|
+
if (pasteImages && text === "") {
|
|
519
|
+
const files = Array.from(data?.files ?? []).filter(
|
|
520
|
+
(f) => f.type.startsWith("image/")
|
|
521
|
+
);
|
|
522
|
+
if (files.length > 0) {
|
|
523
|
+
return files.map((file) => ({ id: holdImage(file), block: false }));
|
|
486
524
|
}
|
|
487
|
-
return next;
|
|
488
|
-
});
|
|
489
|
-
}, []);
|
|
490
|
-
const composeOutgoing = useCallback(() => {
|
|
491
|
-
if (inline) return expandMarkers(value, heldPastes, heldImages);
|
|
492
|
-
return [value, ...heldPastes.map((p) => p.text)].filter(Boolean).join("\n\n");
|
|
493
|
-
}, [inline, value, heldPastes, heldImages]);
|
|
494
|
-
const hasContent = value.trim().length > 0 || !inline && heldPastes.length > 0;
|
|
495
|
-
const focusInput = useCallback(() => {
|
|
496
|
-
if (inline) editorRef.current?.focus();
|
|
497
|
-
else textareaRef.current?.focus();
|
|
498
|
-
}, [inline]);
|
|
499
|
-
const handleSubmit = useCallback(() => {
|
|
500
|
-
if (!hasContent || disabled) return;
|
|
501
|
-
const order = markedIds(value);
|
|
502
|
-
const images = inline ? order.flatMap((id) => heldImages.filter((i) => i.id === id)) : [];
|
|
503
|
-
onSubmit?.(composeOutgoing(), { images });
|
|
504
|
-
setValue("");
|
|
505
|
-
pills.clear();
|
|
506
|
-
setHeldImages([]);
|
|
507
|
-
focusInput();
|
|
508
|
-
}, [
|
|
509
|
-
hasContent,
|
|
510
|
-
disabled,
|
|
511
|
-
value,
|
|
512
|
-
inline,
|
|
513
|
-
heldImages,
|
|
514
|
-
onSubmit,
|
|
515
|
-
composeOutgoing,
|
|
516
|
-
setValue,
|
|
517
|
-
pills,
|
|
518
|
-
setHeldImages,
|
|
519
|
-
focusInput
|
|
520
|
-
]);
|
|
521
|
-
const handleKeyDown = (e) => {
|
|
522
|
-
if (e.key === "Enter" && !e.shiftKey) {
|
|
523
|
-
e.preventDefault();
|
|
524
|
-
handleSubmit();
|
|
525
525
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
(paste) => {
|
|
529
|
-
const text = pills.expand(paste.id);
|
|
530
|
-
if (text === void 0) return;
|
|
531
|
-
setValue((current) => current ? `${current}
|
|
532
|
-
|
|
533
|
-
${text}` : text);
|
|
534
|
-
textareaRef.current?.focus();
|
|
535
|
-
},
|
|
536
|
-
[pills, setValue]
|
|
537
|
-
);
|
|
538
|
-
const expandInline = useCallback(
|
|
539
|
-
(id) => {
|
|
540
|
-
const text = pills.expand(id);
|
|
541
|
-
if (text === void 0) return;
|
|
542
|
-
editorRef.current?.replacePill(id, text);
|
|
543
|
-
},
|
|
544
|
-
[pills]
|
|
545
|
-
);
|
|
546
|
-
return /* @__PURE__ */ jsxs(
|
|
547
|
-
"div",
|
|
548
|
-
{
|
|
549
|
-
"data-react-fancy-composer": "",
|
|
550
|
-
ref,
|
|
551
|
-
className: cn(
|
|
552
|
-
"rounded-xl border border-zinc-200 bg-white dark:border-zinc-700 dark:bg-zinc-900",
|
|
553
|
-
disabled && "opacity-50",
|
|
554
|
-
className
|
|
555
|
-
),
|
|
556
|
-
children: [
|
|
557
|
-
!inline && heldPastes.length > 0 && /* @__PURE__ */ jsx(
|
|
558
|
-
"div",
|
|
559
|
-
{
|
|
560
|
-
"data-react-fancy-composer-paste-pills": "",
|
|
561
|
-
className: "flex flex-wrap gap-1.5 px-3 pt-3",
|
|
562
|
-
children: heldPastes.map((paste) => (
|
|
563
|
-
// `group` + `group-hover` rather than hover state: the preview is
|
|
564
|
-
// presentation, and a useState for it would be one more thing an
|
|
565
|
-
// agent could desynchronise.
|
|
566
|
-
/* @__PURE__ */ jsxs("span", { className: "group relative inline-flex", children: [
|
|
567
|
-
/* @__PURE__ */ jsx(
|
|
568
|
-
"button",
|
|
569
|
-
{
|
|
570
|
-
type: "button",
|
|
571
|
-
"data-react-fancy-composer-paste-pill": paste.id,
|
|
572
|
-
onClick: () => insertPaste(paste),
|
|
573
|
-
disabled,
|
|
574
|
-
"aria-label": `Insert this pasted text, ${withSeparators(paste.text.length)} characters`,
|
|
575
|
-
"aria-describedby": `${paste.id}-preview`,
|
|
576
|
-
className: cn(
|
|
577
|
-
"inline-flex items-center gap-1.5 rounded-full border border-zinc-200 bg-zinc-50 py-1 pl-2.5 pr-7 text-xs text-zinc-700 transition-colors hover:bg-zinc-100 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-200 dark:hover:bg-zinc-700",
|
|
578
|
-
classNames?.pill,
|
|
579
|
-
classNames?.textPill
|
|
580
|
-
),
|
|
581
|
-
children: /* @__PURE__ */ jsxs("span", { children: [
|
|
582
|
-
"Pasted text \xB7 ",
|
|
583
|
-
withSeparators(paste.text.length),
|
|
584
|
-
" characters"
|
|
585
|
-
] })
|
|
586
|
-
}
|
|
587
|
-
),
|
|
588
|
-
/* @__PURE__ */ jsx(
|
|
589
|
-
"button",
|
|
590
|
-
{
|
|
591
|
-
type: "button",
|
|
592
|
-
"data-react-fancy-composer-paste-pill-remove": paste.id,
|
|
593
|
-
onClick: () => pills.remove(paste.id),
|
|
594
|
-
disabled,
|
|
595
|
-
"aria-label": "Discard this pasted text",
|
|
596
|
-
className: cn(
|
|
597
|
-
"absolute right-1 top-1/2 -translate-y-1/2 rounded-full px-1 text-xs text-zinc-400 transition-colors hover:text-zinc-700 dark:hover:text-zinc-100",
|
|
598
|
-
classNames?.remove
|
|
599
|
-
),
|
|
600
|
-
children: "\xD7"
|
|
601
|
-
}
|
|
602
|
-
),
|
|
603
|
-
/* @__PURE__ */ jsx(
|
|
604
|
-
"span",
|
|
605
|
-
{
|
|
606
|
-
id: `${paste.id}-preview`,
|
|
607
|
-
"data-react-fancy-composer-paste-pill-preview": paste.id,
|
|
608
|
-
role: "tooltip",
|
|
609
|
-
className: cn(
|
|
610
|
-
"pointer-events-none absolute bottom-full left-0 z-10 mb-1 hidden max-w-xs whitespace-pre-wrap break-all rounded-md bg-zinc-900 px-2 py-1 text-xs text-white group-hover:block dark:bg-zinc-100 dark:text-zinc-900",
|
|
611
|
-
classNames?.preview
|
|
612
|
-
),
|
|
613
|
-
children: renderPastePreview?.({ kind: "text", paste }) ?? endsOf(paste.text, previewChars)
|
|
614
|
-
}
|
|
615
|
-
)
|
|
616
|
-
] }, paste.id)
|
|
617
|
-
))
|
|
618
|
-
}
|
|
619
|
-
),
|
|
620
|
-
inline ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
621
|
-
/* @__PURE__ */ jsx(
|
|
622
|
-
InlinePasteEditor,
|
|
623
|
-
{
|
|
624
|
-
ref: editorRef,
|
|
625
|
-
value,
|
|
626
|
-
known,
|
|
627
|
-
onChange: handleInlineChange,
|
|
628
|
-
onSubmit: handleSubmit,
|
|
629
|
-
onPaste: decidePaste,
|
|
630
|
-
onHostsChange: handleHostsChange,
|
|
631
|
-
placeholder,
|
|
632
|
-
disabled,
|
|
633
|
-
className: classNames?.input
|
|
634
|
-
}
|
|
635
|
-
),
|
|
636
|
-
Array.from(hosts, ([id, host]) => {
|
|
637
|
-
const paste = heldPastes.find((p) => p.id === id);
|
|
638
|
-
const image = paste ? void 0 : heldImages.find((i) => i.id === id);
|
|
639
|
-
const preview = paste ? { kind: "text", paste } : image ? { kind: "image", image } : null;
|
|
640
|
-
return preview ? createPortal(
|
|
641
|
-
/* @__PURE__ */ jsx(
|
|
642
|
-
InlinePill,
|
|
643
|
-
{
|
|
644
|
-
preview,
|
|
645
|
-
previewChars,
|
|
646
|
-
renderPastePreview,
|
|
647
|
-
classNames,
|
|
648
|
-
disabled,
|
|
649
|
-
onExpand: expandInline
|
|
650
|
-
}
|
|
651
|
-
),
|
|
652
|
-
host,
|
|
653
|
-
id
|
|
654
|
-
) : null;
|
|
655
|
-
})
|
|
656
|
-
] }) : /* @__PURE__ */ jsx(
|
|
657
|
-
"textarea",
|
|
658
|
-
{
|
|
659
|
-
ref: textareaRef,
|
|
660
|
-
value,
|
|
661
|
-
onChange: (e) => setValue(e.target.value),
|
|
662
|
-
onKeyDown: handleKeyDown,
|
|
663
|
-
onPaste: handlePaste,
|
|
664
|
-
placeholder,
|
|
665
|
-
disabled,
|
|
666
|
-
rows: 3,
|
|
667
|
-
className: cn(
|
|
668
|
-
"w-full resize-none bg-transparent px-4 pt-3 text-sm outline-none placeholder:text-zinc-400",
|
|
669
|
-
classNames?.input
|
|
670
|
-
)
|
|
671
|
-
}
|
|
672
|
-
),
|
|
673
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-t border-zinc-100 px-3 py-2 dark:border-zinc-800", children: [
|
|
674
|
-
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-1", children: actions }),
|
|
675
|
-
/* @__PURE__ */ jsx(
|
|
676
|
-
"button",
|
|
677
|
-
{
|
|
678
|
-
type: "button",
|
|
679
|
-
onClick: handleSubmit,
|
|
680
|
-
disabled: disabled || !hasContent,
|
|
681
|
-
className: "rounded-lg bg-zinc-900 px-3 py-1.5 text-sm font-medium text-white transition-colors hover:bg-zinc-800 disabled:opacity-50 dark:bg-white dark:text-zinc-900 dark:hover:bg-zinc-100",
|
|
682
|
-
children: "Send"
|
|
683
|
-
}
|
|
684
|
-
)
|
|
685
|
-
] })
|
|
686
|
-
]
|
|
526
|
+
if (pasteThreshold && pasteThreshold > 0 && text.length >= pasteThreshold) {
|
|
527
|
+
return [{ id: pills.hold(text), block: true }];
|
|
687
528
|
}
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
529
|
+
return null;
|
|
530
|
+
},
|
|
531
|
+
[pasteImages, pasteThreshold, holdImage, pills]
|
|
532
|
+
);
|
|
533
|
+
const prune = useCallback(
|
|
534
|
+
(next) => {
|
|
535
|
+
const present = new Set(markedIds(next));
|
|
536
|
+
for (const paste of heldPastes) if (!present.has(paste.id)) pills.remove(paste.id);
|
|
537
|
+
if (heldImages.some((i) => !present.has(i.id))) {
|
|
538
|
+
setHeldImages((current) => current.filter((i) => present.has(i.id)));
|
|
539
|
+
}
|
|
540
|
+
},
|
|
541
|
+
[heldPastes, heldImages, pills, setHeldImages]
|
|
542
|
+
);
|
|
543
|
+
const [hosts, setHosts] = useState(/* @__PURE__ */ new Map());
|
|
544
|
+
const onHostsChange = useCallback((next) => {
|
|
545
|
+
setHosts((prev) => {
|
|
546
|
+
if (prev.size === next.size && [...next].every(([id, el]) => prev.get(id) === el)) {
|
|
547
|
+
return prev;
|
|
548
|
+
}
|
|
549
|
+
return next;
|
|
550
|
+
});
|
|
551
|
+
}, []);
|
|
552
|
+
const expandPill = useCallback(
|
|
553
|
+
(id) => {
|
|
554
|
+
const text = pills.expand(id);
|
|
555
|
+
if (text === void 0) return;
|
|
556
|
+
editorRef.current?.replacePill(id, text);
|
|
557
|
+
},
|
|
558
|
+
[pills, editorRef]
|
|
559
|
+
);
|
|
560
|
+
const expand = useCallback(
|
|
561
|
+
(value) => expandMarkers(value, heldPastes, heldImages),
|
|
562
|
+
[heldPastes, heldImages]
|
|
563
|
+
);
|
|
564
|
+
const imagesIn = useCallback(
|
|
565
|
+
(value) => markedIds(value).flatMap((id) => heldImages.filter((i) => i.id === id)),
|
|
566
|
+
[heldImages]
|
|
567
|
+
);
|
|
568
|
+
const clear = useCallback(() => {
|
|
569
|
+
pills.clear();
|
|
570
|
+
setHeldImages([]);
|
|
571
|
+
}, [pills, setHeldImages]);
|
|
572
|
+
const portals = Array.from(hosts, ([id, host]) => {
|
|
573
|
+
const paste = heldPastes.find((p) => p.id === id);
|
|
574
|
+
const image = paste ? void 0 : heldImages.find((i) => i.id === id);
|
|
575
|
+
const preview = paste ? { kind: "text", paste } : image ? { kind: "image", image } : null;
|
|
576
|
+
return preview ? createPortal(
|
|
577
|
+
/* @__PURE__ */ jsx(
|
|
578
|
+
InlinePill,
|
|
579
|
+
{
|
|
580
|
+
preview,
|
|
581
|
+
previewChars,
|
|
582
|
+
renderPastePreview,
|
|
583
|
+
classNames,
|
|
584
|
+
disabled,
|
|
585
|
+
onExpand: expandPill
|
|
586
|
+
}
|
|
587
|
+
),
|
|
588
|
+
host,
|
|
589
|
+
id
|
|
590
|
+
) : null;
|
|
591
|
+
});
|
|
592
|
+
return {
|
|
593
|
+
pills,
|
|
594
|
+
heldPastes,
|
|
595
|
+
heldImages,
|
|
596
|
+
known,
|
|
597
|
+
decidePaste,
|
|
598
|
+
prune,
|
|
599
|
+
onHostsChange,
|
|
600
|
+
expand,
|
|
601
|
+
imagesIn,
|
|
602
|
+
clear,
|
|
603
|
+
portals
|
|
604
|
+
};
|
|
605
|
+
}
|
|
692
606
|
|
|
693
|
-
export {
|
|
694
|
-
//# sourceMappingURL=chunk-
|
|
695
|
-
//# sourceMappingURL=chunk-
|
|
607
|
+
export { InlinePasteEditor, endsOf, pasteMarker, useInlinePaste, usePastePills, withSeparators };
|
|
608
|
+
//# sourceMappingURL=chunk-2JOWGYAJ.js.map
|
|
609
|
+
//# sourceMappingURL=chunk-2JOWGYAJ.js.map
|