@particle-academy/react-fancy 5.37.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.
Files changed (64) hide show
  1. package/CHANGELOG.md +133 -0
  2. package/dist/{index-C8PKWA_5.d.cts → Composer.types-CuSPnaOt.d.cts} +103 -7
  3. package/dist/{index-C8PKWA_5.d.ts → Composer.types-CuSPnaOt.d.ts} +103 -7
  4. package/dist/autocomplete.cjs +3 -3
  5. package/dist/autocomplete.js +2 -2
  6. package/dist/chunk-2JOWGYAJ.js +609 -0
  7. package/dist/chunk-2JOWGYAJ.js.map +1 -0
  8. package/dist/chunk-6TZPM43Q.cjs +616 -0
  9. package/dist/chunk-6TZPM43Q.cjs.map +1 -0
  10. package/dist/{chunk-FXP4JLGU.cjs → chunk-AR3JOPGT.cjs} +3 -3
  11. package/dist/{chunk-FXP4JLGU.cjs.map → chunk-AR3JOPGT.cjs.map} +1 -1
  12. package/dist/{chunk-23FTDEGR.cjs → chunk-BZOU7VJQ.cjs} +97 -84
  13. package/dist/chunk-BZOU7VJQ.cjs.map +1 -0
  14. package/dist/{chunk-35EN5AYE.js → chunk-CF5KRDN4.js} +6 -3
  15. package/dist/chunk-CF5KRDN4.js.map +1 -0
  16. package/dist/{chunk-DNV7MUEE.cjs → chunk-FJ2GMQLC.cjs} +117 -27
  17. package/dist/chunk-FJ2GMQLC.cjs.map +1 -0
  18. package/dist/{chunk-6ASZUE4T.js → chunk-FQLL2JI7.js} +3 -3
  19. package/dist/{chunk-6ASZUE4T.js.map → chunk-FQLL2JI7.js.map} +1 -1
  20. package/dist/{chunk-ZH3SPBVB.js → chunk-IECPLOSZ.js} +3 -3
  21. package/dist/{chunk-ZH3SPBVB.js.map → chunk-IECPLOSZ.js.map} +1 -1
  22. package/dist/{chunk-E2SZBIAV.cjs → chunk-IWQ3ZFLZ.cjs} +5 -5
  23. package/dist/{chunk-E2SZBIAV.cjs.map → chunk-IWQ3ZFLZ.cjs.map} +1 -1
  24. package/dist/{chunk-QBNR5IVM.cjs → chunk-MFBUK4RV.cjs} +3 -3
  25. package/dist/{chunk-QBNR5IVM.cjs.map → chunk-MFBUK4RV.cjs.map} +1 -1
  26. package/dist/{chunk-2XAVAPDA.js → chunk-MXAVGOHW.js} +99 -85
  27. package/dist/chunk-MXAVGOHW.js.map +1 -0
  28. package/dist/{chunk-ISPCXIJZ.cjs → chunk-X7H75WVP.cjs} +6 -3
  29. package/dist/chunk-X7H75WVP.cjs.map +1 -0
  30. package/dist/{chunk-O3WEV562.js → chunk-YLPVTEUH.js} +118 -28
  31. package/dist/chunk-YLPVTEUH.js.map +1 -0
  32. package/dist/{chunk-O2ZHORSC.js → chunk-Z6RVBZQ4.js} +3 -3
  33. package/dist/{chunk-O2ZHORSC.js.map → chunk-Z6RVBZQ4.js.map} +1 -1
  34. package/dist/composer.cjs +10 -2
  35. package/dist/composer.d.cts +7 -2
  36. package/dist/composer.d.ts +7 -2
  37. package/dist/composer.js +5 -1
  38. package/dist/editor.cjs +1 -1
  39. package/dist/editor.js +1 -1
  40. package/dist/{index-DXUAAZzE.d.ts → index-BsIBOwaK.d.cts} +46 -1
  41. package/dist/{index-DXUAAZzE.d.cts → index-S4FC1yFK.d.ts} +46 -1
  42. package/dist/index.cjs +40 -35
  43. package/dist/index.d.cts +3 -2
  44. package/dist/index.d.ts +3 -2
  45. package/dist/index.js +8 -7
  46. package/dist/inputs.cjs +3 -3
  47. package/dist/inputs.js +2 -2
  48. package/dist/json-editor.cjs +14 -14
  49. package/dist/json-editor.js +3 -3
  50. package/dist/prompt-input.cjs +5 -3
  51. package/dist/prompt-input.d.cts +2 -1
  52. package/dist/prompt-input.d.ts +2 -1
  53. package/dist/prompt-input.js +4 -2
  54. package/dist/select.cjs +3 -3
  55. package/dist/select.js +2 -2
  56. package/dist/tooltip.cjs +2 -2
  57. package/dist/tooltip.js +1 -1
  58. package/package.json +1 -1
  59. package/dist/chunk-23FTDEGR.cjs.map +0 -1
  60. package/dist/chunk-2XAVAPDA.js.map +0 -1
  61. package/dist/chunk-35EN5AYE.js.map +0 -1
  62. package/dist/chunk-DNV7MUEE.cjs.map +0 -1
  63. package/dist/chunk-ISPCXIJZ.cjs.map +0 -1
  64. package/dist/chunk-O3WEV562.js.map +0 -1
@@ -0,0 +1,609 @@
1
+ import { Tooltip } from './chunk-CF5KRDN4.js';
2
+ import { useControllableState } from './chunk-EDYAXGUM.js';
3
+ import { cn } from './chunk-MMW2JGKZ.js';
4
+ import { forwardRef, useRef, useCallback, useLayoutEffect, useImperativeHandle, useMemo, useState, useEffect } from 'react';
5
+ import { jsxs, jsx } from 'react/jsx-runtime';
6
+ import { createPortal } from 'react-dom';
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
+ }
36
+ function usePastePills({
37
+ threshold,
38
+ heldPastes: controlledHeldPastes,
39
+ onHeldPastesChange
40
+ } = {}) {
41
+ const [heldPastes, setHeldPastes] = useControllableState(
42
+ controlledHeldPastes,
43
+ [],
44
+ onHeldPastesChange
45
+ );
46
+ const nextId = useRef(0);
47
+ const hold = useCallback(
48
+ (text) => {
49
+ let id;
50
+ do {
51
+ nextId.current += 1;
52
+ id = `paste-${nextId.current}`;
53
+ } while (heldPastes.some((p) => p.id === id));
54
+ setHeldPastes((current) => [...current, { id, text }]);
55
+ return id;
56
+ },
57
+ [heldPastes, setHeldPastes]
58
+ );
59
+ const handlePaste = useCallback(
60
+ (event) => {
61
+ if (!threshold || threshold <= 0) return false;
62
+ const text = event.clipboardData?.getData("text/plain") ?? "";
63
+ if (text.length < threshold) return false;
64
+ event.preventDefault();
65
+ hold(text);
66
+ return true;
67
+ },
68
+ [threshold, hold]
69
+ );
70
+ const expand = useCallback(
71
+ (id) => {
72
+ const found = heldPastes.find((p) => p.id === id);
73
+ if (found === void 0) return void 0;
74
+ setHeldPastes((current) => current.filter((p) => p.id !== id));
75
+ return found.text;
76
+ },
77
+ [heldPastes, setHeldPastes]
78
+ );
79
+ const remove = useCallback(
80
+ (id) => setHeldPastes((current) => current.filter((p) => p.id !== id)),
81
+ [setHeldPastes]
82
+ );
83
+ const clear = useCallback(() => setHeldPastes([]), [setHeldPastes]);
84
+ return { heldPastes, handlePaste, hold, expand, remove, clear };
85
+ }
86
+ function withSeparators(n) {
87
+ return String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
88
+ }
89
+ function endsOf(text, chars) {
90
+ if (text.length <= chars * 2) return text;
91
+ return `${text.slice(0, chars)} \u2026 ${text.slice(-chars)}`;
92
+ }
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 });
106
+ }
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
+ ) });
147
+ }
148
+ var HOST_ATTR = "data-react-fancy-composer-paste-host";
149
+ function serialize(parent, keep) {
150
+ let out = "";
151
+ const children = Array.from(parent.childNodes);
152
+ children.forEach((node, i) => {
153
+ if (node.nodeType === Node.TEXT_NODE) {
154
+ out += (node.textContent ?? "").replace(/\u00A0/g, " ");
155
+ return;
156
+ }
157
+ if (!(node instanceof HTMLElement)) return;
158
+ const id = node.getAttribute(HOST_ATTR);
159
+ if (id !== null) {
160
+ if (keep(id)) out += pasteMarker(id);
161
+ return;
162
+ }
163
+ if (node.tagName === "BR") {
164
+ if (i !== children.length - 1) out += "\n";
165
+ return;
166
+ }
167
+ if (/^(DIV|P)$/.test(node.tagName) && out.length > 0 && !out.endsWith("\n")) {
168
+ out += "\n";
169
+ }
170
+ out += serialize(node, keep);
171
+ });
172
+ return out;
173
+ }
174
+ function makeHost(id) {
175
+ const host = document.createElement("span");
176
+ host.setAttribute("contenteditable", "false");
177
+ host.setAttribute(HOST_ATTR, id);
178
+ host.className = "inline-block align-baseline";
179
+ return host;
180
+ }
181
+ function ensureTrailingBreak(root, value) {
182
+ const last = root.lastChild;
183
+ const hasBreak = last instanceof HTMLElement && last.tagName === "BR";
184
+ if (value.endsWith("\n") && !hasBreak) root.append(document.createElement("br"));
185
+ if (!value.endsWith("\n") && hasBreak && value.length > 0) last.remove();
186
+ }
187
+ function placeCaretAfter(node) {
188
+ const selection = window.getSelection();
189
+ if (!selection) return;
190
+ const range = document.createRange();
191
+ range.setStartAfter(node);
192
+ range.collapse(true);
193
+ selection.removeAllRanges();
194
+ selection.addRange(range);
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
+ }
234
+ var InlinePasteEditor = forwardRef(
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) {
250
+ const rootRef = useRef(null);
251
+ const lastValue = useRef(null);
252
+ const pending = useRef(/* @__PURE__ */ new Set());
253
+ const knownRef = useRef(known);
254
+ knownRef.current = known;
255
+ const keep = useCallback(
256
+ (id) => knownRef.current.has(id) || pending.current.has(id),
257
+ []
258
+ );
259
+ const reportHosts = useCallback(() => {
260
+ const root = rootRef.current;
261
+ if (!root) return;
262
+ const hosts = /* @__PURE__ */ new Map();
263
+ root.querySelectorAll(`[${HOST_ATTR}]`).forEach((el) => {
264
+ const id = el.getAttribute(HOST_ATTR);
265
+ if (keep(id)) hosts.set(id, el);
266
+ });
267
+ onHostsChange(hosts);
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
+ );
283
+ const emit = useCallback(() => {
284
+ const root = rootRef.current;
285
+ if (!root) return;
286
+ const next = serialize(root, keep);
287
+ ensureTrailingBreak(root, next);
288
+ reportHosts();
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);
305
+ lastValue.current = next;
306
+ reportHosts();
307
+ },
308
+ [reportHosts]
309
+ );
310
+ const caret = useCallback(() => {
311
+ const root = rootRef.current;
312
+ const selection = window.getSelection();
313
+ if (selection && selection.rangeCount > 0) {
314
+ const range2 = selection.getRangeAt(0);
315
+ if (root.contains(range2.commonAncestorContainer)) return range2;
316
+ }
317
+ const range = document.createRange();
318
+ range.selectNodeContents(root);
319
+ range.collapse(false);
320
+ const last = root.lastChild;
321
+ if (last instanceof HTMLElement && last.tagName === "BR") {
322
+ range.setStartBefore(last);
323
+ range.collapse(true);
324
+ }
325
+ return range;
326
+ }, []);
327
+ const insert = useCallback(
328
+ (build) => {
329
+ const root = rootRef.current;
330
+ const range = caret();
331
+ range.deleteContents();
332
+ const head = document.createRange();
333
+ head.selectNodeContents(root);
334
+ head.setEnd(range.startContainer, range.startOffset);
335
+ const tail = document.createRange();
336
+ tail.selectNodeContents(root);
337
+ tail.setStart(range.endContainer, range.endOffset);
338
+ const nodes = build(
339
+ serialize(head.cloneContents(), () => true),
340
+ serialize(tail.cloneContents(), () => true)
341
+ );
342
+ if (nodes.length === 0) return;
343
+ const fragment = document.createDocumentFragment();
344
+ nodes.forEach((n) => fragment.append(n));
345
+ range.insertNode(fragment);
346
+ placeCaretAfter(nodes[nodes.length - 1]);
347
+ emit();
348
+ },
349
+ [caret, emit]
350
+ );
351
+ const insertText = useCallback(
352
+ (text) => insert(() => text ? [document.createTextNode(text)] : []),
353
+ [insert]
354
+ );
355
+ useLayoutEffect(() => {
356
+ const root = rootRef.current;
357
+ if (!root) return;
358
+ for (const id of pending.current) if (known.has(id)) pending.current.delete(id);
359
+ const wanted = segmentValue(value, known).flatMap((s) => s.type === "pill" ? [s.id] : []).join(PILL_MARK);
360
+ const onScreen = Array.from(root.querySelectorAll(`[${HOST_ATTR}]`)).map((el) => el.getAttribute(HOST_ATTR)).filter((id) => id !== null && keep(id)).join(PILL_MARK);
361
+ if (value === lastValue.current && wanted === onScreen) return;
362
+ render(value);
363
+ }, [value, known, keep, render]);
364
+ useImperativeHandle(
365
+ ref,
366
+ () => ({
367
+ focus() {
368
+ const root = rootRef.current;
369
+ if (!root) return;
370
+ root.focus();
371
+ const range = caret();
372
+ const selection = window.getSelection();
373
+ selection?.removeAllRanges();
374
+ selection?.addRange(range);
375
+ },
376
+ replacePill(id, text) {
377
+ const root = rootRef.current;
378
+ const host = Array.from(
379
+ root?.querySelectorAll(`[${HOST_ATTR}]`) ?? []
380
+ ).find((el) => el.getAttribute(HOST_ATTR) === id);
381
+ if (!root || !host) return;
382
+ const node = document.createTextNode(text);
383
+ host.replaceWith(node);
384
+ root.focus();
385
+ placeCaretAfter(node);
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();
395
+ }
396
+ }),
397
+ [caret, emit, render, keep]
398
+ );
399
+ const handlePaste = (e) => {
400
+ e.preventDefault();
401
+ if (disabled) return;
402
+ const pills = onPaste(e.clipboardData);
403
+ if (pills === null) {
404
+ insertText((e.clipboardData?.getData("text/plain") ?? "").replace(/\r\n?/g, "\n"));
405
+ return;
406
+ }
407
+ for (const { id } of pills) pending.current.add(id);
408
+ insert((before, after) => {
409
+ const nodes = [];
410
+ for (const { id, block } of pills) {
411
+ if (block) {
412
+ const precededByBreak = nodes.length === 0 ? before === "" || before.endsWith("\n") : nodes[nodes.length - 1].textContent === "\n";
413
+ if (!precededByBreak) nodes.push(document.createTextNode("\n"));
414
+ nodes.push(makeHost(id));
415
+ nodes.push(document.createTextNode("\n"));
416
+ } else {
417
+ nodes.push(makeHost(id));
418
+ }
419
+ }
420
+ const last = nodes[nodes.length - 1];
421
+ if (last.textContent === "\n" && after.startsWith("\n")) nodes.pop();
422
+ return nodes;
423
+ });
424
+ };
425
+ const handleKeyDown = (e) => {
426
+ onKeyDown?.(e);
427
+ if (e.defaultPrevented) return;
428
+ if (e.key !== "Enter" || e.nativeEvent.isComposing) return;
429
+ e.preventDefault();
430
+ const sends = submitKey === "mod-enter" ? e.ctrlKey || e.metaKey : !e.shiftKey;
431
+ if (sends) {
432
+ onSubmit();
433
+ } else {
434
+ insertText("\n");
435
+ }
436
+ };
437
+ return /* @__PURE__ */ jsxs("div", { className: "relative", children: [
438
+ value === "" && /* @__PURE__ */ jsx(
439
+ "span",
440
+ {
441
+ "aria-hidden": "true",
442
+ className: cn(
443
+ "pointer-events-none absolute left-4 top-3 text-sm text-zinc-400",
444
+ placeholderClassName
445
+ ),
446
+ children: placeholder
447
+ }
448
+ ),
449
+ /* @__PURE__ */ jsx(
450
+ "div",
451
+ {
452
+ ref: rootRef,
453
+ "data-react-fancy-composer-input": "",
454
+ role: "textbox",
455
+ "aria-multiline": "true",
456
+ "aria-placeholder": placeholder,
457
+ "aria-disabled": disabled || void 0,
458
+ contentEditable: !disabled,
459
+ suppressContentEditableWarning: true,
460
+ onInput: emit,
461
+ onPaste: handlePaste,
462
+ onKeyDown: handleKeyDown,
463
+ style: maxHeight === void 0 ? void 0 : { maxHeight },
464
+ className: cn(
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",
466
+ className
467
+ )
468
+ }
469
+ )
470
+ ] });
471
+ }
472
+ );
473
+ function useInlinePaste({
474
+ pasteThreshold,
475
+ pasteImages = false,
476
+ previewChars,
477
+ renderPastePreview,
478
+ classNames,
479
+ disabled,
480
+ heldPastes: controlledHeldPastes,
481
+ onHeldPastesChange,
482
+ heldImages: controlledHeldImages,
483
+ onHeldImagesChange,
484
+ editorRef
485
+ }) {
486
+ const pills = usePastePills({
487
+ threshold: pasteThreshold,
488
+ heldPastes: controlledHeldPastes,
489
+ onHeldPastesChange
490
+ });
491
+ const heldPastes = pills.heldPastes;
492
+ const [heldImages, setHeldImages] = useControllableState(
493
+ controlledHeldImages,
494
+ [],
495
+ onHeldImagesChange
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 }));
524
+ }
525
+ }
526
+ if (pasteThreshold && pasteThreshold > 0 && text.length >= pasteThreshold) {
527
+ return [{ id: pills.hold(text), block: true }];
528
+ }
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
+ }
606
+
607
+ export { InlinePasteEditor, endsOf, pasteMarker, useInlinePaste, usePastePills, withSeparators };
608
+ //# sourceMappingURL=chunk-2JOWGYAJ.js.map
609
+ //# sourceMappingURL=chunk-2JOWGYAJ.js.map