@particle-academy/react-fancy 5.43.0 → 5.44.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 +31 -0
  2. package/dist/{Composer.types-CuSPnaOt.d.cts → PastePills.types-CDyotkUb.d.cts} +35 -82
  3. package/dist/{Composer.types-CuSPnaOt.d.ts → PastePills.types-CDyotkUb.d.ts} +35 -82
  4. package/dist/PastedFilesInput-CrgNoEBd.d.ts +141 -0
  5. package/dist/PastedFilesInput-DQ1eZaHk.d.cts +141 -0
  6. package/dist/{chunk-IPNJMHJ3.cjs → chunk-2QIGXSSZ.cjs} +105 -3
  7. package/dist/chunk-2QIGXSSZ.cjs.map +1 -0
  8. package/dist/{chunk-BZOU7VJQ.cjs → chunk-BR27AXFE.cjs} +16 -10
  9. package/dist/chunk-BR27AXFE.cjs.map +1 -0
  10. package/dist/{chunk-G4WYCWIQ.js → chunk-DMFJJ73P.js} +3 -3
  11. package/dist/{chunk-G4WYCWIQ.js.map → chunk-DMFJJ73P.js.map} +1 -1
  12. package/dist/{chunk-2VIDYJUG.js → chunk-EEWRLFTI.js} +107 -5
  13. package/dist/chunk-EEWRLFTI.js.map +1 -0
  14. package/dist/{chunk-SF5OAVGD.js → chunk-JZCAMPIA.js} +12 -4
  15. package/dist/chunk-JZCAMPIA.js.map +1 -0
  16. package/dist/{chunk-2JOWGYAJ.js → chunk-KYZNCVRD.js} +213 -179
  17. package/dist/chunk-KYZNCVRD.js.map +1 -0
  18. package/dist/{chunk-MXAVGOHW.js → chunk-L637KPEA.js} +12 -6
  19. package/dist/chunk-L637KPEA.js.map +1 -0
  20. package/dist/{chunk-6TZPM43Q.cjs → chunk-MSRYJETY.cjs} +219 -178
  21. package/dist/chunk-MSRYJETY.cjs.map +1 -0
  22. package/dist/{chunk-K4NHVCY6.cjs → chunk-S5ZYG5MG.cjs} +8 -8
  23. package/dist/{chunk-K4NHVCY6.cjs.map → chunk-S5ZYG5MG.cjs.map} +1 -1
  24. package/dist/{chunk-UUBHUSUP.cjs → chunk-Z3FIU77M.cjs} +14 -6
  25. package/dist/chunk-Z3FIU77M.cjs.map +1 -0
  26. package/dist/composer.cjs +4 -4
  27. package/dist/composer.d.cts +93 -3
  28. package/dist/composer.d.ts +93 -3
  29. package/dist/composer.js +2 -2
  30. package/dist/{index-S4FC1yFK.d.ts → index-BF5qsQur.d.cts} +8 -2
  31. package/dist/{index-BsIBOwaK.d.cts → index-BZON-gXr.d.ts} +8 -2
  32. package/dist/index.cjs +75 -63
  33. package/dist/index.d.cts +4 -3
  34. package/dist/index.d.ts +4 -3
  35. package/dist/index.js +12 -12
  36. package/dist/input.cjs +6 -2
  37. package/dist/input.d.cts +34 -0
  38. package/dist/input.d.ts +34 -0
  39. package/dist/input.js +5 -1
  40. package/dist/inputs.cjs +8 -6
  41. package/dist/inputs.d.cts +1 -0
  42. package/dist/inputs.d.ts +1 -0
  43. package/dist/inputs.js +4 -2
  44. package/dist/json-editor.cjs +15 -13
  45. package/dist/json-editor.js +4 -2
  46. package/dist/paste-pills.cjs +65 -0
  47. package/dist/paste-pills.cjs.map +1 -0
  48. package/dist/paste-pills.d.cts +34 -0
  49. package/dist/paste-pills.d.ts +34 -0
  50. package/dist/paste-pills.js +8 -0
  51. package/dist/paste-pills.js.map +1 -0
  52. package/dist/prompt-input.cjs +3 -3
  53. package/dist/prompt-input.d.cts +2 -2
  54. package/dist/prompt-input.d.ts +2 -2
  55. package/dist/prompt-input.js +2 -2
  56. package/package.json +11 -1
  57. package/dist/chunk-2JOWGYAJ.js.map +0 -1
  58. package/dist/chunk-2VIDYJUG.js.map +0 -1
  59. package/dist/chunk-6TZPM43Q.cjs.map +0 -1
  60. package/dist/chunk-BZOU7VJQ.cjs.map +0 -1
  61. package/dist/chunk-IPNJMHJ3.cjs.map +0 -1
  62. package/dist/chunk-MXAVGOHW.js.map +0 -1
  63. package/dist/chunk-SF5OAVGD.js.map +0 -1
  64. package/dist/chunk-UUBHUSUP.cjs.map +0 -1
package/CHANGELOG.md CHANGED
@@ -11,6 +11,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [5.44.0] - 2026-10-19
15
+
16
+ ### Added
17
+
18
+ - **`Input` holds a long paste as a pill**, as `Composer` and `PromptInput` do:
19
+ `<Input name="q" pasteThreshold={500} />`. The pill sits in the field (one
20
+ line, so no line of its own); clicking it puts the full text back. A hidden
21
+ `<input name={name}>` posts the value with every pill expanded, so a plain
22
+ `<form>` submit sends the whole paste. Enter still submits the form.
23
+ **Omit `pasteThreshold` and `pasteImages` and nothing changes**: it is the same
24
+ `<input>` as before. With either set, `ref` points at the hidden input,
25
+ `onValueChange` receives the value with a marker per pill, and `onChange` is
26
+ not called.
27
+ - **Pasted files reach the form.** `pasteImages` on `Input` holds a pasted image
28
+ as a pill AND puts the file in a hidden `<input type="file" multiple>` in the
29
+ same form (`{name}_files[]` by default, or `pasteFilesName`), kept in step with
30
+ the pills: delete one and its file leaves the input. `Composer` and
31
+ `PromptInput` take `pasteFilesName` for the same hidden input.
32
+ - **`PastePills`** (`@particle-academy/react-fancy/paste-pills`): the paste-pill
33
+ core in one place: `usePastePills`, `useInlinePaste`, `InlinePasteEditor`,
34
+ `PastedFilesInput` and the marker helpers. `Composer`, `PromptInput` and
35
+ `Input` all import it, so `npx fancy-cli add` copies it once, as a dependency,
36
+ instead of a copy per component.
37
+
38
+ ### Changed
39
+
40
+ - The paste-pill files moved from `components/Composer/` and
41
+ `hooks/use-paste-pills.ts` into `components/PastePills/`. **What to do:**
42
+ nothing. Every public export is unchanged, and `hooks/use-paste-pills` still
43
+ re-exports the hook.
44
+
14
45
  ## [5.43.0] - 2026-10-17
15
46
 
16
47
  ### Fixed
@@ -1,5 +1,3 @@
1
- import { ReactNode } from 'react';
2
-
3
1
  /**
4
2
  * A paste held beside a composer rather than inserted into it.
5
3
  *
@@ -137,8 +135,39 @@ interface HeldImage {
137
135
  file: File;
138
136
  label: string;
139
137
  }
138
+ /**
139
+ * U+FFFC OBJECT REPLACEMENT CHARACTER — Unicode's own "an embedded object sits
140
+ * here". A marker is the id wrapped in a pair of them.
141
+ *
142
+ * Why the message carries a marker at all: with inline placement the message
143
+ * ITSELF is the record of what is attached and where, so there is one answer to
144
+ * "what am I sending?" rather than a text plus a parallel list that can disagree
145
+ * the moment someone backspaces a pill. This is the MOIC estate's design for their
146
+ * own composer, and the reason they withdrew from the beside-the-input layout.
147
+ *
148
+ * Only a marker whose id the composer actually holds is a pill; anything else is
149
+ * left as text, so a stray U+FFFC in pasted content cannot invent an attachment.
150
+ */
151
+ declare const PILL_MARK = "\uFFFC";
140
152
  /** The marker for a held paste or image, for a host or agent writing `value` itself. */
141
153
  declare function pasteMarker(id: string): string;
154
+ type ValueSegment = {
155
+ type: "text";
156
+ text: string;
157
+ } | {
158
+ type: "pill";
159
+ id: string;
160
+ };
161
+ /** Split a value into text and pills. Markers for ids not in `known` stay text. */
162
+ declare function segmentValue(value: string, known: ReadonlySet<string>): ValueSegment[];
163
+ /** The id of every marker the value contains, in order, held or not. */
164
+ declare function markedIds(value: string): string[];
165
+ /**
166
+ * What actually gets sent: every text pill replaced by its full text, every image
167
+ * pill by `[Pasted image N]` so the reader of the message can see where the image
168
+ * sat. The images themselves go to `onSubmit`'s second argument.
169
+ */
170
+ declare function expandMarkers(value: string, pastes: readonly HeldPaste[], images: readonly HeldImage[]): string;
142
171
 
143
172
  /** What a hover preview is being asked to show. */
144
173
  type ComposerPastePreview = {
@@ -166,84 +195,8 @@ interface ComposerPasteClassNames {
166
195
  /** The editable surface itself. */
167
196
  input?: string;
168
197
  }
169
- interface ComposerSubmitExtras {
170
- /**
171
- * Images still pasted into the message when it was sent, in the order they
172
- * appear in it. Each one's place in the sent text is marked `[Pasted image N]`,
173
- * matching its `label`.
174
- */
175
- images: HeldImage[];
176
- }
177
- interface ComposerProps {
178
- value?: string;
179
- defaultValue?: string;
180
- onChange?: (value: string) => void;
181
- /**
182
- * The second argument only ever carries anything with `pasteImages` on. A
183
- * one-argument handler stays valid.
184
- */
185
- onSubmit?: (value: string, extras: ComposerSubmitExtras) => void;
186
- placeholder?: string;
187
- actions?: ReactNode;
188
- disabled?: boolean;
189
- className?: string;
190
- /**
191
- * Hold a paste of at least this many characters as a pill, instead of inserting
192
- * it. Try 500 — about a long paragraph.
193
- *
194
- * **Omit it and paste behaves exactly as it always has.** Opt-in on purpose:
195
- * paste is a fundamental interaction and this library is past 1.0, so a minor
196
- * release must not quietly change what pasting does for every consumer. There is
197
- * no default threshold for the same reason.
198
- *
199
- * Below the threshold nothing changes at all — no prevented event, no pill, no
200
- * ceremony for an address or a name.
201
- */
202
- pasteThreshold?: number;
203
- /**
204
- * Where a pill goes.
205
- *
206
- * - `"above"` (default, and the only behaviour before 5.38.0) — in a row above
207
- * the input; a send appends held text after what was typed.
208
- * - `"inline"` — **in the message, where the paste happened.** A text pill gets
209
- * a line to itself; clicking it puts the full text back exactly there; an
210
- * image pill sits in the sentence. Backspace deletes a pill like a character.
211
- *
212
- * With `"inline"`, `value` carries a marker for each pill (see `pasteMarker`),
213
- * because the message is then the one record of what is attached and where.
214
- * `onSubmit` always receives the expanded text, never a marker.
215
- */
216
- pastePlacement?: "above" | "inline";
217
- /**
218
- * Hold a pasted IMAGE as a pill (`"inline"` placement only). Off by default —
219
- * turning it on is a promise to read `onSubmit`'s second argument, because a pill
220
- * reads as "attached" and an image a host never sends is an attachment the
221
- * person believes went and did not.
222
- *
223
- * A clipboard carrying text as well as an image (copying from a document often
224
- * includes a picture of the selection) pastes the TEXT: the image there is a
225
- * rendering, not the thing that was copied.
226
- */
227
- pasteImages?: boolean;
228
- /** Characters of a held text paste shown at EACH end of its hover preview. Default 20. */
229
- previewChars?: number;
230
- /** Replace the hover popover's content. Its box is styled by `classNames.preview`. */
231
- renderPastePreview?: (preview: ComposerPastePreview) => ReactNode;
232
- /** Restyle the pills and their popover. */
233
- classNames?: ComposerPasteClassNames;
234
- /**
235
- * The held pastes, if you want to own them.
236
- *
237
- * Controllable rather than internal because the component contract forbids
238
- * internal-only state for anything an agent might need to read or write — an
239
- * agent driving this surface has to be able to see that a paste is attached, and
240
- * to attach one itself.
241
- */
242
- heldPastes?: HeldPaste[];
243
- onHeldPastesChange?: (pastes: HeldPaste[]) => void;
244
- /** The held images, if you want to own them. Same reasoning as `heldPastes`. */
245
- heldImages?: HeldImage[];
246
- onHeldImagesChange?: (images: HeldImage[]) => void;
247
- }
198
+ /** The same shapes under the names they have outside `Composer`. */
199
+ type PastePreview = ComposerPastePreview;
200
+ type PasteClassNames = ComposerPasteClassNames;
248
201
 
249
- export { type ComposerPasteClassNames as C, type HeldImage as H, type PasteEventLike as P, type UsePastePillsOptions as U, type ComposerPastePreview as a, type ComposerProps as b, type ComposerSubmitExtras as c, type HeldPaste as d, type UsePastePillsResult as e, pasteMarker as p, usePastePills as u };
202
+ export { type ComposerPastePreview as C, type HeldImage as H, PILL_MARK as P, type UsePastePillsOptions as U, type ValueSegment as V, type ComposerPasteClassNames as a, type HeldPaste as b, type PasteClassNames as c, type PasteEventLike as d, type PastePreview as e, type UsePastePillsResult as f, expandMarkers as g, markedIds as m, pasteMarker as p, segmentValue as s, usePastePills as u };
@@ -1,5 +1,3 @@
1
- import { ReactNode } from 'react';
2
-
3
1
  /**
4
2
  * A paste held beside a composer rather than inserted into it.
5
3
  *
@@ -137,8 +135,39 @@ interface HeldImage {
137
135
  file: File;
138
136
  label: string;
139
137
  }
138
+ /**
139
+ * U+FFFC OBJECT REPLACEMENT CHARACTER — Unicode's own "an embedded object sits
140
+ * here". A marker is the id wrapped in a pair of them.
141
+ *
142
+ * Why the message carries a marker at all: with inline placement the message
143
+ * ITSELF is the record of what is attached and where, so there is one answer to
144
+ * "what am I sending?" rather than a text plus a parallel list that can disagree
145
+ * the moment someone backspaces a pill. This is the MOIC estate's design for their
146
+ * own composer, and the reason they withdrew from the beside-the-input layout.
147
+ *
148
+ * Only a marker whose id the composer actually holds is a pill; anything else is
149
+ * left as text, so a stray U+FFFC in pasted content cannot invent an attachment.
150
+ */
151
+ declare const PILL_MARK = "\uFFFC";
140
152
  /** The marker for a held paste or image, for a host or agent writing `value` itself. */
141
153
  declare function pasteMarker(id: string): string;
154
+ type ValueSegment = {
155
+ type: "text";
156
+ text: string;
157
+ } | {
158
+ type: "pill";
159
+ id: string;
160
+ };
161
+ /** Split a value into text and pills. Markers for ids not in `known` stay text. */
162
+ declare function segmentValue(value: string, known: ReadonlySet<string>): ValueSegment[];
163
+ /** The id of every marker the value contains, in order, held or not. */
164
+ declare function markedIds(value: string): string[];
165
+ /**
166
+ * What actually gets sent: every text pill replaced by its full text, every image
167
+ * pill by `[Pasted image N]` so the reader of the message can see where the image
168
+ * sat. The images themselves go to `onSubmit`'s second argument.
169
+ */
170
+ declare function expandMarkers(value: string, pastes: readonly HeldPaste[], images: readonly HeldImage[]): string;
142
171
 
143
172
  /** What a hover preview is being asked to show. */
144
173
  type ComposerPastePreview = {
@@ -166,84 +195,8 @@ interface ComposerPasteClassNames {
166
195
  /** The editable surface itself. */
167
196
  input?: string;
168
197
  }
169
- interface ComposerSubmitExtras {
170
- /**
171
- * Images still pasted into the message when it was sent, in the order they
172
- * appear in it. Each one's place in the sent text is marked `[Pasted image N]`,
173
- * matching its `label`.
174
- */
175
- images: HeldImage[];
176
- }
177
- interface ComposerProps {
178
- value?: string;
179
- defaultValue?: string;
180
- onChange?: (value: string) => void;
181
- /**
182
- * The second argument only ever carries anything with `pasteImages` on. A
183
- * one-argument handler stays valid.
184
- */
185
- onSubmit?: (value: string, extras: ComposerSubmitExtras) => void;
186
- placeholder?: string;
187
- actions?: ReactNode;
188
- disabled?: boolean;
189
- className?: string;
190
- /**
191
- * Hold a paste of at least this many characters as a pill, instead of inserting
192
- * it. Try 500 — about a long paragraph.
193
- *
194
- * **Omit it and paste behaves exactly as it always has.** Opt-in on purpose:
195
- * paste is a fundamental interaction and this library is past 1.0, so a minor
196
- * release must not quietly change what pasting does for every consumer. There is
197
- * no default threshold for the same reason.
198
- *
199
- * Below the threshold nothing changes at all — no prevented event, no pill, no
200
- * ceremony for an address or a name.
201
- */
202
- pasteThreshold?: number;
203
- /**
204
- * Where a pill goes.
205
- *
206
- * - `"above"` (default, and the only behaviour before 5.38.0) — in a row above
207
- * the input; a send appends held text after what was typed.
208
- * - `"inline"` — **in the message, where the paste happened.** A text pill gets
209
- * a line to itself; clicking it puts the full text back exactly there; an
210
- * image pill sits in the sentence. Backspace deletes a pill like a character.
211
- *
212
- * With `"inline"`, `value` carries a marker for each pill (see `pasteMarker`),
213
- * because the message is then the one record of what is attached and where.
214
- * `onSubmit` always receives the expanded text, never a marker.
215
- */
216
- pastePlacement?: "above" | "inline";
217
- /**
218
- * Hold a pasted IMAGE as a pill (`"inline"` placement only). Off by default —
219
- * turning it on is a promise to read `onSubmit`'s second argument, because a pill
220
- * reads as "attached" and an image a host never sends is an attachment the
221
- * person believes went and did not.
222
- *
223
- * A clipboard carrying text as well as an image (copying from a document often
224
- * includes a picture of the selection) pastes the TEXT: the image there is a
225
- * rendering, not the thing that was copied.
226
- */
227
- pasteImages?: boolean;
228
- /** Characters of a held text paste shown at EACH end of its hover preview. Default 20. */
229
- previewChars?: number;
230
- /** Replace the hover popover's content. Its box is styled by `classNames.preview`. */
231
- renderPastePreview?: (preview: ComposerPastePreview) => ReactNode;
232
- /** Restyle the pills and their popover. */
233
- classNames?: ComposerPasteClassNames;
234
- /**
235
- * The held pastes, if you want to own them.
236
- *
237
- * Controllable rather than internal because the component contract forbids
238
- * internal-only state for anything an agent might need to read or write — an
239
- * agent driving this surface has to be able to see that a paste is attached, and
240
- * to attach one itself.
241
- */
242
- heldPastes?: HeldPaste[];
243
- onHeldPastesChange?: (pastes: HeldPaste[]) => void;
244
- /** The held images, if you want to own them. Same reasoning as `heldPastes`. */
245
- heldImages?: HeldImage[];
246
- onHeldImagesChange?: (images: HeldImage[]) => void;
247
- }
198
+ /** The same shapes under the names they have outside `Composer`. */
199
+ type PastePreview = ComposerPastePreview;
200
+ type PasteClassNames = ComposerPasteClassNames;
248
201
 
249
- export { type ComposerPasteClassNames as C, type HeldImage as H, type PasteEventLike as P, type UsePastePillsOptions as U, type ComposerPastePreview as a, type ComposerProps as b, type ComposerSubmitExtras as c, type HeldPaste as d, type UsePastePillsResult as e, pasteMarker as p, usePastePills as u };
202
+ export { type ComposerPastePreview as C, type HeldImage as H, PILL_MARK as P, type UsePastePillsOptions as U, type ValueSegment as V, type ComposerPasteClassNames as a, type HeldPaste as b, type PasteClassNames as c, type PasteEventLike as d, type PastePreview as e, type UsePastePillsResult as f, expandMarkers as g, markedIds as m, pasteMarker as p, segmentValue as s, usePastePills as u };
@@ -0,0 +1,141 @@
1
+ import * as react from 'react';
2
+ import { ReactNode, RefObject } from 'react';
3
+ import { C as ComposerPastePreview, a as ComposerPasteClassNames, b as HeldPaste, H as HeldImage, f as UsePastePillsResult } from './PastePills.types-CDyotkUb.js';
4
+
5
+ /** The attribute on the placeholder span a pill is portalled into. */
6
+ declare const HOST_ATTR = "data-react-fancy-composer-paste-host";
7
+ /** What a paste becomes, as decided by `Composer`. `block` pills get their own line. */
8
+ interface PillToInsert {
9
+ id: string;
10
+ block: boolean;
11
+ }
12
+ interface InlinePasteEditorHandle {
13
+ focus(): void;
14
+ /** Swap a pill for plain text, where it sits. */
15
+ replacePill(id: string, text: string): void;
16
+ /**
17
+ * Replace the whole value and put the caret at `caret` (an offset into the
18
+ * value). For a host that edits text itself — `PromptInput`'s `/` and `@`
19
+ * pickers — where rebuilding from `value` alone would lose the caret.
20
+ */
21
+ setValue(value: string, caret: number): void;
22
+ }
23
+ interface InlinePasteEditorProps {
24
+ value: string;
25
+ /** Ids the composer holds. A marker for anything else stays text. */
26
+ known: ReadonlySet<string>;
27
+ /** `caret` is the caret's offset into `value`, or `value.length` if it is elsewhere. */
28
+ onChange(value: string, caret: number): void;
29
+ onSubmit(): void;
30
+ /**
31
+ * Which key sends. `"enter"` (default): Enter sends, Shift+Enter is a newline.
32
+ * `"mod-enter"`: Ctrl/⌘+Enter sends and a plain Enter is a newline.
33
+ */
34
+ submitKey?: "enter" | "mod-enter";
35
+ /** Runs BEFORE the editor's own key handling; `preventDefault()` to claim the key. */
36
+ onKeyDown?(e: React.KeyboardEvent<HTMLDivElement>): void;
37
+ placeholderClassName?: string;
38
+ /** Height in px past which the input scrolls. Defaults to the class's max height. */
39
+ maxHeight?: number;
40
+ /** Return the pills a paste becomes, or null to insert its text as typed. */
41
+ onPaste(data: DataTransfer | null): PillToInsert[] | null;
42
+ /** Every pill placeholder currently in the editor, for the composer to portal into. */
43
+ onHostsChange(hosts: ReadonlyMap<string, HTMLElement>): void;
44
+ placeholder: string;
45
+ disabled: boolean;
46
+ className?: string;
47
+ /**
48
+ * `false` makes it a single-line field, as `Input` uses it: Enter always
49
+ * submits, and a newline in pasted text becomes a space, as a browser does for
50
+ * an `<input>`. A held paste keeps its newlines; it is only the field that has
51
+ * one line. Default `true`.
52
+ */
53
+ multiline?: boolean;
54
+ /** The editable surface's id, so a host can find it. */
55
+ id?: string;
56
+ /** Names the textbox for assistive tech; a `<div>` cannot be the target of a `<label>`. */
57
+ ariaLabelledBy?: string;
58
+ ariaInvalid?: boolean;
59
+ style?: React.CSSProperties;
60
+ }
61
+ /**
62
+ * The plain-text-plus-pills surface behind `Composer`'s inline placement.
63
+ *
64
+ * The DOM is the source of truth WHILE TYPING and `value` is its serialisation;
65
+ * the DOM is rebuilt from `value` only when `value` changes from outside (a send,
66
+ * a host, an agent). Rebuilding on every keystroke would throw the caret away.
67
+ *
68
+ * Every insertion is done here by hand, never by the browser: a paste is always
69
+ * intercepted, so no foreign HTML ever lands in the editor, and Enter is handled
70
+ * so a browser never invents a `<div>` per line.
71
+ */
72
+ declare const InlinePasteEditor: react.ForwardRefExoticComponent<InlinePasteEditorProps & react.RefAttributes<InlinePasteEditorHandle>>;
73
+
74
+ interface UseInlinePasteOptions {
75
+ pasteThreshold?: number;
76
+ pasteImages?: boolean;
77
+ previewChars: number;
78
+ renderPastePreview?: (preview: ComposerPastePreview) => ReactNode;
79
+ classNames?: ComposerPasteClassNames;
80
+ disabled: boolean;
81
+ heldPastes?: HeldPaste[];
82
+ onHeldPastesChange?: (pastes: HeldPaste[]) => void;
83
+ heldImages?: HeldImage[];
84
+ onHeldImagesChange?: (images: HeldImage[]) => void;
85
+ editorRef: RefObject<InlinePasteEditorHandle | null>;
86
+ /**
87
+ * Give a text pill a line to itself. Default `true`; a single-line field
88
+ * (`Input`) has no lines to give, so its pills sit in the text.
89
+ */
90
+ blockText?: boolean;
91
+ }
92
+ /**
93
+ * Everything inline paste pills need besides the editor itself: the held pastes
94
+ * and images, deciding what a paste becomes, dropping a pill's hold when it is
95
+ * deleted from the message, rendering the pills into the editor, and expanding
96
+ * them on send.
97
+ *
98
+ * Shared by `Composer`, `PromptInput` and `Input` so they cannot disagree about
99
+ * what a pill means. The rule that must not drift is the one `expand` and `imagesIn`
100
+ * carry: a send with pills still closed carries everything they hold.
101
+ */
102
+ declare function useInlinePaste({ pasteThreshold, pasteImages, previewChars, renderPastePreview, classNames, disabled, heldPastes: controlledHeldPastes, onHeldPastesChange, heldImages: controlledHeldImages, onHeldImagesChange, editorRef, blockText, }: UseInlinePasteOptions): {
103
+ pills: UsePastePillsResult;
104
+ heldPastes: HeldPaste[];
105
+ heldImages: HeldImage[];
106
+ known: Set<string>;
107
+ decidePaste: (data: DataTransfer | null) => PillToInsert[] | null;
108
+ prune: (next: string) => void;
109
+ onHostsChange: (next: ReadonlyMap<string, HTMLElement>) => void;
110
+ expand: (value: string) => string;
111
+ imagesIn: (value: string) => HeldImage[];
112
+ clear: () => void;
113
+ portals: (react.ReactPortal | null)[];
114
+ };
115
+
116
+ interface PastedFilesInputProps {
117
+ /** The form field name the files are submitted under. */
118
+ name: string;
119
+ /** The files to submit, in the order the message holds them. */
120
+ files: readonly File[];
121
+ /** Associate with a `<form id>` elsewhere in the document, as `<input form>` does. */
122
+ form?: string;
123
+ }
124
+ /**
125
+ * A hidden `<input type="file">` carrying the files pasted into a field, so a
126
+ * plain `<form>` submits them with everything else.
127
+ *
128
+ * A pasted file is not in any form field: the clipboard hands it to the paste
129
+ * handler and nowhere else. Held as a pill, it is attached as far as the person can
130
+ * see, and a native submit would then post the text without it. A file input is
131
+ * the one element a form posts files from, and its `files` can be set from a
132
+ * `DataTransfer`, so the pills keep it in step: delete a pill and its file leaves
133
+ * the input too.
134
+ *
135
+ * Hidden rather than visually hidden: `hidden` keeps it out of the tab order and
136
+ * the accessibility tree, where the pills already represent it, and does not stop
137
+ * a form submitting it.
138
+ */
139
+ declare function PastedFilesInput({ name, files, form }: PastedFilesInputProps): react.JSX.Element;
140
+
141
+ export { HOST_ATTR as H, InlinePasteEditor as I, PastedFilesInput as P, type UseInlinePasteOptions as U, type InlinePasteEditorHandle as a, type PastedFilesInputProps as b, type PillToInsert as c, useInlinePaste as u };
@@ -0,0 +1,141 @@
1
+ import * as react from 'react';
2
+ import { ReactNode, RefObject } from 'react';
3
+ import { C as ComposerPastePreview, a as ComposerPasteClassNames, b as HeldPaste, H as HeldImage, f as UsePastePillsResult } from './PastePills.types-CDyotkUb.cjs';
4
+
5
+ /** The attribute on the placeholder span a pill is portalled into. */
6
+ declare const HOST_ATTR = "data-react-fancy-composer-paste-host";
7
+ /** What a paste becomes, as decided by `Composer`. `block` pills get their own line. */
8
+ interface PillToInsert {
9
+ id: string;
10
+ block: boolean;
11
+ }
12
+ interface InlinePasteEditorHandle {
13
+ focus(): void;
14
+ /** Swap a pill for plain text, where it sits. */
15
+ replacePill(id: string, text: string): void;
16
+ /**
17
+ * Replace the whole value and put the caret at `caret` (an offset into the
18
+ * value). For a host that edits text itself — `PromptInput`'s `/` and `@`
19
+ * pickers — where rebuilding from `value` alone would lose the caret.
20
+ */
21
+ setValue(value: string, caret: number): void;
22
+ }
23
+ interface InlinePasteEditorProps {
24
+ value: string;
25
+ /** Ids the composer holds. A marker for anything else stays text. */
26
+ known: ReadonlySet<string>;
27
+ /** `caret` is the caret's offset into `value`, or `value.length` if it is elsewhere. */
28
+ onChange(value: string, caret: number): void;
29
+ onSubmit(): void;
30
+ /**
31
+ * Which key sends. `"enter"` (default): Enter sends, Shift+Enter is a newline.
32
+ * `"mod-enter"`: Ctrl/⌘+Enter sends and a plain Enter is a newline.
33
+ */
34
+ submitKey?: "enter" | "mod-enter";
35
+ /** Runs BEFORE the editor's own key handling; `preventDefault()` to claim the key. */
36
+ onKeyDown?(e: React.KeyboardEvent<HTMLDivElement>): void;
37
+ placeholderClassName?: string;
38
+ /** Height in px past which the input scrolls. Defaults to the class's max height. */
39
+ maxHeight?: number;
40
+ /** Return the pills a paste becomes, or null to insert its text as typed. */
41
+ onPaste(data: DataTransfer | null): PillToInsert[] | null;
42
+ /** Every pill placeholder currently in the editor, for the composer to portal into. */
43
+ onHostsChange(hosts: ReadonlyMap<string, HTMLElement>): void;
44
+ placeholder: string;
45
+ disabled: boolean;
46
+ className?: string;
47
+ /**
48
+ * `false` makes it a single-line field, as `Input` uses it: Enter always
49
+ * submits, and a newline in pasted text becomes a space, as a browser does for
50
+ * an `<input>`. A held paste keeps its newlines; it is only the field that has
51
+ * one line. Default `true`.
52
+ */
53
+ multiline?: boolean;
54
+ /** The editable surface's id, so a host can find it. */
55
+ id?: string;
56
+ /** Names the textbox for assistive tech; a `<div>` cannot be the target of a `<label>`. */
57
+ ariaLabelledBy?: string;
58
+ ariaInvalid?: boolean;
59
+ style?: React.CSSProperties;
60
+ }
61
+ /**
62
+ * The plain-text-plus-pills surface behind `Composer`'s inline placement.
63
+ *
64
+ * The DOM is the source of truth WHILE TYPING and `value` is its serialisation;
65
+ * the DOM is rebuilt from `value` only when `value` changes from outside (a send,
66
+ * a host, an agent). Rebuilding on every keystroke would throw the caret away.
67
+ *
68
+ * Every insertion is done here by hand, never by the browser: a paste is always
69
+ * intercepted, so no foreign HTML ever lands in the editor, and Enter is handled
70
+ * so a browser never invents a `<div>` per line.
71
+ */
72
+ declare const InlinePasteEditor: react.ForwardRefExoticComponent<InlinePasteEditorProps & react.RefAttributes<InlinePasteEditorHandle>>;
73
+
74
+ interface UseInlinePasteOptions {
75
+ pasteThreshold?: number;
76
+ pasteImages?: boolean;
77
+ previewChars: number;
78
+ renderPastePreview?: (preview: ComposerPastePreview) => ReactNode;
79
+ classNames?: ComposerPasteClassNames;
80
+ disabled: boolean;
81
+ heldPastes?: HeldPaste[];
82
+ onHeldPastesChange?: (pastes: HeldPaste[]) => void;
83
+ heldImages?: HeldImage[];
84
+ onHeldImagesChange?: (images: HeldImage[]) => void;
85
+ editorRef: RefObject<InlinePasteEditorHandle | null>;
86
+ /**
87
+ * Give a text pill a line to itself. Default `true`; a single-line field
88
+ * (`Input`) has no lines to give, so its pills sit in the text.
89
+ */
90
+ blockText?: boolean;
91
+ }
92
+ /**
93
+ * Everything inline paste pills need besides the editor itself: the held pastes
94
+ * and images, deciding what a paste becomes, dropping a pill's hold when it is
95
+ * deleted from the message, rendering the pills into the editor, and expanding
96
+ * them on send.
97
+ *
98
+ * Shared by `Composer`, `PromptInput` and `Input` so they cannot disagree about
99
+ * what a pill means. The rule that must not drift is the one `expand` and `imagesIn`
100
+ * carry: a send with pills still closed carries everything they hold.
101
+ */
102
+ declare function useInlinePaste({ pasteThreshold, pasteImages, previewChars, renderPastePreview, classNames, disabled, heldPastes: controlledHeldPastes, onHeldPastesChange, heldImages: controlledHeldImages, onHeldImagesChange, editorRef, blockText, }: UseInlinePasteOptions): {
103
+ pills: UsePastePillsResult;
104
+ heldPastes: HeldPaste[];
105
+ heldImages: HeldImage[];
106
+ known: Set<string>;
107
+ decidePaste: (data: DataTransfer | null) => PillToInsert[] | null;
108
+ prune: (next: string) => void;
109
+ onHostsChange: (next: ReadonlyMap<string, HTMLElement>) => void;
110
+ expand: (value: string) => string;
111
+ imagesIn: (value: string) => HeldImage[];
112
+ clear: () => void;
113
+ portals: (react.ReactPortal | null)[];
114
+ };
115
+
116
+ interface PastedFilesInputProps {
117
+ /** The form field name the files are submitted under. */
118
+ name: string;
119
+ /** The files to submit, in the order the message holds them. */
120
+ files: readonly File[];
121
+ /** Associate with a `<form id>` elsewhere in the document, as `<input form>` does. */
122
+ form?: string;
123
+ }
124
+ /**
125
+ * A hidden `<input type="file">` carrying the files pasted into a field, so a
126
+ * plain `<form>` submits them with everything else.
127
+ *
128
+ * A pasted file is not in any form field: the clipboard hands it to the paste
129
+ * handler and nowhere else. Held as a pill, it is attached as far as the person can
130
+ * see, and a native submit would then post the text without it. A file input is
131
+ * the one element a form posts files from, and its `files` can be set from a
132
+ * `DataTransfer`, so the pills keep it in step: delete a pill and its file leaves
133
+ * the input too.
134
+ *
135
+ * Hidden rather than visually hidden: `hidden` keeps it out of the tab order and
136
+ * the accessibility tree, where the pills already represent it, and does not stop
137
+ * a form submitting it.
138
+ */
139
+ declare function PastedFilesInput({ name, files, form }: PastedFilesInputProps): react.JSX.Element;
140
+
141
+ export { HOST_ATTR as H, InlinePasteEditor as I, PastedFilesInput as P, type UseInlinePasteOptions as U, type InlinePasteEditorHandle as a, type PastedFilesInputProps as b, type PillToInsert as c, useInlinePaste as u };