@particle-academy/react-fancy 5.64.0 → 5.65.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.
@@ -2,6 +2,96 @@ import * as react from 'react';
2
2
  import { HTMLAttributes, ReactNode } from 'react';
3
3
  import { C as ComposerPastePreview, a as ComposerPasteClassNames } from './PastePills.types-CDyotkUb.cjs';
4
4
 
5
+ /**
6
+ * A file in a saved draft — an attachment chip, or the image behind an image
7
+ * pill — described by data alone, so the draft survives `JSON.stringify`.
8
+ *
9
+ * The bytes are deliberately NOT in here. A `File` does not survive JSON, and
10
+ * putting base64 in the draft by default would make every saved draft as large
11
+ * as everything attached to it: a 6 MB screenshot becomes 8 MB of string written
12
+ * on every keystroke by a host that saves on change. Instead the host decides
13
+ * where bytes live, through `persistAttachment` (save) and `restoreAttachment`
14
+ * (load), and the draft carries only the host's handle for them in `ref`.
15
+ *
16
+ * A host that WANTS the bytes inline passes `promptFileToDataUrl` as
17
+ * `persistAttachment`, and `ref` is then a `data:` URL.
18
+ */
19
+ interface PromptDraftFile {
20
+ /** Stable for the life of the draft. For an image pill, the pill's own id. */
21
+ id: string;
22
+ name: string;
23
+ /** MIME type, `""` when the browser did not know it. */
24
+ type: string;
25
+ bytes: number;
26
+ /**
27
+ * The host's handle for the stored bytes — whatever `persistAttachment`
28
+ * returned: a storage key, an upload URL, a `data:` URL. Absent when the host
29
+ * keeps nothing, in which case a restored attachment has no `File`.
30
+ */
31
+ ref?: string;
32
+ }
33
+ /** A paste held as a pill inside the message. Its marker sits in `text`. */
34
+ type PromptDraftPill = {
35
+ kind: "text";
36
+ id: string;
37
+ text: string;
38
+ } | {
39
+ kind: "image";
40
+ id: string;
41
+ label: string;
42
+ file: PromptDraftFile;
43
+ };
44
+ /**
45
+ * Everything a PromptInput holds, as JSON. Versioned, so a draft saved by one
46
+ * release and read by a later one is recognised or refused, never guessed at.
47
+ *
48
+ * `text` is the raw value: with paste pills on, each pill is a marker in it
49
+ * (`pasteMarker(id)`), because the message itself is the record of what is
50
+ * attached and where. Mentions and slash commands are plain text in it.
51
+ */
52
+ interface PromptDraft {
53
+ v: 1;
54
+ text: string;
55
+ pills: PromptDraftPill[];
56
+ attachments: PromptDraftFile[];
57
+ }
58
+ interface PromptSetDraftOptions {
59
+ /**
60
+ * Focus the composer after restoring, caret at the end. Default `false`:
61
+ * restoring a draft never moves focus or anybody's caret on its own
62
+ * (genie#1235 was exactly that).
63
+ */
64
+ focus?: boolean;
65
+ }
66
+ /** The imperative handle a `ref` on `PromptInput` receives. */
67
+ interface PromptInputHandle {
68
+ /** The current draft. A fresh JSON-safe copy; mutating it changes nothing. */
69
+ getDraft(): PromptDraft;
70
+ /**
71
+ * Replace the whole draft. Does NOT call `onChange` / `onDraftChange` (the
72
+ * host is the one setting it), does NOT focus unless `{ focus: true }`.
73
+ * A draft whose `v` this release does not know is refused with a warning.
74
+ */
75
+ setDraft(draft: PromptDraft, options?: PromptSetDraftOptions): void;
76
+ /** Focus the composer, caret at the end. */
77
+ focus(): void;
78
+ }
79
+ /**
80
+ * Read a File as a `data:` URL. Pass it as `persistAttachment` to keep the bytes
81
+ * IN the draft — simple, and fine for small files; every saved draft then carries
82
+ * every attached byte, base64-inflated by a third.
83
+ */
84
+ declare function promptFileToDataUrl(file: Blob): Promise<string>;
85
+ /**
86
+ * Turn a `data:` URL back into a File — the other half of `promptFileToDataUrl`:
87
+ *
88
+ * restoreAttachment={(f) => f.ref ? promptDataUrlToFile(f.ref, f.name, f.type) : undefined}
89
+ *
90
+ * Synchronous, so the restored draft has its Files on the first render.
91
+ * `undefined` for anything that is not a `data:` URL.
92
+ */
93
+ declare function promptDataUrlToFile(dataUrl: string, name: string, type?: string): File | undefined;
94
+
5
95
  /**
6
96
  * Which modifier key the user actually has.
7
97
  *
@@ -98,6 +188,12 @@ type PromptAttachment = {
98
188
  file?: File;
99
189
  /** MIME type. Hosts branch on this to decide whether a file can be sent as-is. */
100
190
  type?: string;
191
+ /**
192
+ * The host's handle for the stored bytes, from `persistAttachment`. On an
193
+ * attachment restored from a draft WITHOUT a File, this is how the host finds
194
+ * the bytes to send.
195
+ */
196
+ ref?: string;
101
197
  };
102
198
  /**
103
199
  * `...rest` reaches the root surface, so a consumer or a bridge can tag the
@@ -108,9 +204,50 @@ type PromptAttachment = {
108
204
  * calls it out: "Each interactive element has a stable identity. Agents never
109
205
  * guess DOM."
110
206
  */
111
- interface PromptInputProps extends Omit<HTMLAttributes<HTMLDivElement>, "onSubmit" | "className"> {
207
+ interface PromptInputProps extends Omit<HTMLAttributes<HTMLDivElement>, "onSubmit" | "className" | "onChange" | "defaultValue"> {
112
208
  /** Extra classes on the root surface. MERGED, never replaced. */
113
209
  className?: string;
210
+ /**
211
+ * The text, controlled. With paste pills on it carries a marker per pill
212
+ * (`pasteMarker(id)`), exactly as `Composer`'s `value` does. Pills and
213
+ * attachments stay internal; use `draft` to control those too.
214
+ */
215
+ value?: string;
216
+ /** The starting text, uncontrolled. Ignored when `value` or `draft` is given. */
217
+ defaultValue?: string;
218
+ /**
219
+ * Called with the new text when the PERSON edits it, and with `""` after a
220
+ * send. Not called by `setDraft` or a prop change: the host made those.
221
+ */
222
+ onChange?: (value: string) => void;
223
+ /**
224
+ * The whole draft, controlled: text, paste pills and attachments, as JSON.
225
+ * Wins over `value`. Pair with `onDraftChange`.
226
+ */
227
+ draft?: PromptDraft;
228
+ /** The starting draft, uncontrolled. Wins over `defaultValue`. */
229
+ defaultDraft?: PromptDraft;
230
+ /**
231
+ * Called with the whole draft whenever the person changes any part of it —
232
+ * typing, a paste, an attach, a removal, a send (which empties it) — and when
233
+ * `persistAttachment` resolves a `ref`. Not called by `setDraft`.
234
+ */
235
+ onDraftChange?: (draft: PromptDraft) => void;
236
+ /**
237
+ * Keep an attached file somewhere and return a handle for it (a storage key, an
238
+ * upload URL — or a `data:` URL via `promptFileToDataUrl`). Called once per
239
+ * attached or pasted file; the result becomes the file's `ref` in the draft.
240
+ * May return a Promise; the draft is updated (and `onDraftChange` fires) when it
241
+ * resolves. Omit it and a draft carries file metadata only.
242
+ */
243
+ persistAttachment?: (file: File, entry: PromptDraftFile) => string | undefined | Promise<string | undefined>;
244
+ /**
245
+ * Get a File back for a draft entry restored without one (from `setDraft`,
246
+ * `defaultDraft` or a controlled `draft`). May return a Promise. Return
247
+ * `undefined` when there is none: the chip or pill still shows, and the
248
+ * attachment reaches `onSubmit` with its `ref` and no `file`.
249
+ */
250
+ restoreAttachment?: (entry: PromptDraftFile) => File | undefined | Promise<File | undefined>;
114
251
  /** Token budget for the meter. */
115
252
  budgetTokens: number;
116
253
  /** Slash-commands. Names must start with `/`. */
@@ -203,6 +340,6 @@ interface PromptInputProps extends Omit<HTMLAttributes<HTMLDivElement>, "onSubmi
203
340
  /** Fraction of `budgetTokens` past which a `"near-limit"` meter takes the warning colour. Default 0.9. */
204
341
  meterWarnAt?: number;
205
342
  }
206
- declare function PromptInput({ budgetTokens, commands, mentions, showHint, platform, onSubmit, placeholder, charsPerToken, mentionColor, maxHeight, aboveInput, className, pasteThreshold, pasteImages, previewChars, renderPastePreview, pasteClassNames, pasteFilesName, attachButton, sendButton, usedTokens, meter, meterShowAt, meterWarnAt, ...rest }: PromptInputProps): react.JSX.Element;
343
+ declare const PromptInput: react.ForwardRefExoticComponent<PromptInputProps & react.RefAttributes<PromptInputHandle>>;
207
344
 
208
- export { type FancyPlatform as F, type PlatformHints as P, type PromptAttachment as a, type PromptCmd as b, PromptInput as c, type PromptInputProps as d, type PromptMention as e, browserPlatformHints as f, detectPlatform as g, modifierKeyLabel as m };
345
+ export { type FancyPlatform as F, type PlatformHints as P, type PromptAttachment as a, type PromptCmd as b, type PromptDraft as c, type PromptDraftFile as d, type PromptDraftPill as e, PromptInput as f, type PromptInputHandle as g, type PromptInputProps as h, type PromptMention as i, type PromptSetDraftOptions as j, browserPlatformHints as k, detectPlatform as l, modifierKeyLabel as m, promptFileToDataUrl as n, promptDataUrlToFile as p };
@@ -2,6 +2,96 @@ import * as react from 'react';
2
2
  import { HTMLAttributes, ReactNode } from 'react';
3
3
  import { C as ComposerPastePreview, a as ComposerPasteClassNames } from './PastePills.types-CDyotkUb.js';
4
4
 
5
+ /**
6
+ * A file in a saved draft — an attachment chip, or the image behind an image
7
+ * pill — described by data alone, so the draft survives `JSON.stringify`.
8
+ *
9
+ * The bytes are deliberately NOT in here. A `File` does not survive JSON, and
10
+ * putting base64 in the draft by default would make every saved draft as large
11
+ * as everything attached to it: a 6 MB screenshot becomes 8 MB of string written
12
+ * on every keystroke by a host that saves on change. Instead the host decides
13
+ * where bytes live, through `persistAttachment` (save) and `restoreAttachment`
14
+ * (load), and the draft carries only the host's handle for them in `ref`.
15
+ *
16
+ * A host that WANTS the bytes inline passes `promptFileToDataUrl` as
17
+ * `persistAttachment`, and `ref` is then a `data:` URL.
18
+ */
19
+ interface PromptDraftFile {
20
+ /** Stable for the life of the draft. For an image pill, the pill's own id. */
21
+ id: string;
22
+ name: string;
23
+ /** MIME type, `""` when the browser did not know it. */
24
+ type: string;
25
+ bytes: number;
26
+ /**
27
+ * The host's handle for the stored bytes — whatever `persistAttachment`
28
+ * returned: a storage key, an upload URL, a `data:` URL. Absent when the host
29
+ * keeps nothing, in which case a restored attachment has no `File`.
30
+ */
31
+ ref?: string;
32
+ }
33
+ /** A paste held as a pill inside the message. Its marker sits in `text`. */
34
+ type PromptDraftPill = {
35
+ kind: "text";
36
+ id: string;
37
+ text: string;
38
+ } | {
39
+ kind: "image";
40
+ id: string;
41
+ label: string;
42
+ file: PromptDraftFile;
43
+ };
44
+ /**
45
+ * Everything a PromptInput holds, as JSON. Versioned, so a draft saved by one
46
+ * release and read by a later one is recognised or refused, never guessed at.
47
+ *
48
+ * `text` is the raw value: with paste pills on, each pill is a marker in it
49
+ * (`pasteMarker(id)`), because the message itself is the record of what is
50
+ * attached and where. Mentions and slash commands are plain text in it.
51
+ */
52
+ interface PromptDraft {
53
+ v: 1;
54
+ text: string;
55
+ pills: PromptDraftPill[];
56
+ attachments: PromptDraftFile[];
57
+ }
58
+ interface PromptSetDraftOptions {
59
+ /**
60
+ * Focus the composer after restoring, caret at the end. Default `false`:
61
+ * restoring a draft never moves focus or anybody's caret on its own
62
+ * (genie#1235 was exactly that).
63
+ */
64
+ focus?: boolean;
65
+ }
66
+ /** The imperative handle a `ref` on `PromptInput` receives. */
67
+ interface PromptInputHandle {
68
+ /** The current draft. A fresh JSON-safe copy; mutating it changes nothing. */
69
+ getDraft(): PromptDraft;
70
+ /**
71
+ * Replace the whole draft. Does NOT call `onChange` / `onDraftChange` (the
72
+ * host is the one setting it), does NOT focus unless `{ focus: true }`.
73
+ * A draft whose `v` this release does not know is refused with a warning.
74
+ */
75
+ setDraft(draft: PromptDraft, options?: PromptSetDraftOptions): void;
76
+ /** Focus the composer, caret at the end. */
77
+ focus(): void;
78
+ }
79
+ /**
80
+ * Read a File as a `data:` URL. Pass it as `persistAttachment` to keep the bytes
81
+ * IN the draft — simple, and fine for small files; every saved draft then carries
82
+ * every attached byte, base64-inflated by a third.
83
+ */
84
+ declare function promptFileToDataUrl(file: Blob): Promise<string>;
85
+ /**
86
+ * Turn a `data:` URL back into a File — the other half of `promptFileToDataUrl`:
87
+ *
88
+ * restoreAttachment={(f) => f.ref ? promptDataUrlToFile(f.ref, f.name, f.type) : undefined}
89
+ *
90
+ * Synchronous, so the restored draft has its Files on the first render.
91
+ * `undefined` for anything that is not a `data:` URL.
92
+ */
93
+ declare function promptDataUrlToFile(dataUrl: string, name: string, type?: string): File | undefined;
94
+
5
95
  /**
6
96
  * Which modifier key the user actually has.
7
97
  *
@@ -98,6 +188,12 @@ type PromptAttachment = {
98
188
  file?: File;
99
189
  /** MIME type. Hosts branch on this to decide whether a file can be sent as-is. */
100
190
  type?: string;
191
+ /**
192
+ * The host's handle for the stored bytes, from `persistAttachment`. On an
193
+ * attachment restored from a draft WITHOUT a File, this is how the host finds
194
+ * the bytes to send.
195
+ */
196
+ ref?: string;
101
197
  };
102
198
  /**
103
199
  * `...rest` reaches the root surface, so a consumer or a bridge can tag the
@@ -108,9 +204,50 @@ type PromptAttachment = {
108
204
  * calls it out: "Each interactive element has a stable identity. Agents never
109
205
  * guess DOM."
110
206
  */
111
- interface PromptInputProps extends Omit<HTMLAttributes<HTMLDivElement>, "onSubmit" | "className"> {
207
+ interface PromptInputProps extends Omit<HTMLAttributes<HTMLDivElement>, "onSubmit" | "className" | "onChange" | "defaultValue"> {
112
208
  /** Extra classes on the root surface. MERGED, never replaced. */
113
209
  className?: string;
210
+ /**
211
+ * The text, controlled. With paste pills on it carries a marker per pill
212
+ * (`pasteMarker(id)`), exactly as `Composer`'s `value` does. Pills and
213
+ * attachments stay internal; use `draft` to control those too.
214
+ */
215
+ value?: string;
216
+ /** The starting text, uncontrolled. Ignored when `value` or `draft` is given. */
217
+ defaultValue?: string;
218
+ /**
219
+ * Called with the new text when the PERSON edits it, and with `""` after a
220
+ * send. Not called by `setDraft` or a prop change: the host made those.
221
+ */
222
+ onChange?: (value: string) => void;
223
+ /**
224
+ * The whole draft, controlled: text, paste pills and attachments, as JSON.
225
+ * Wins over `value`. Pair with `onDraftChange`.
226
+ */
227
+ draft?: PromptDraft;
228
+ /** The starting draft, uncontrolled. Wins over `defaultValue`. */
229
+ defaultDraft?: PromptDraft;
230
+ /**
231
+ * Called with the whole draft whenever the person changes any part of it —
232
+ * typing, a paste, an attach, a removal, a send (which empties it) — and when
233
+ * `persistAttachment` resolves a `ref`. Not called by `setDraft`.
234
+ */
235
+ onDraftChange?: (draft: PromptDraft) => void;
236
+ /**
237
+ * Keep an attached file somewhere and return a handle for it (a storage key, an
238
+ * upload URL — or a `data:` URL via `promptFileToDataUrl`). Called once per
239
+ * attached or pasted file; the result becomes the file's `ref` in the draft.
240
+ * May return a Promise; the draft is updated (and `onDraftChange` fires) when it
241
+ * resolves. Omit it and a draft carries file metadata only.
242
+ */
243
+ persistAttachment?: (file: File, entry: PromptDraftFile) => string | undefined | Promise<string | undefined>;
244
+ /**
245
+ * Get a File back for a draft entry restored without one (from `setDraft`,
246
+ * `defaultDraft` or a controlled `draft`). May return a Promise. Return
247
+ * `undefined` when there is none: the chip or pill still shows, and the
248
+ * attachment reaches `onSubmit` with its `ref` and no `file`.
249
+ */
250
+ restoreAttachment?: (entry: PromptDraftFile) => File | undefined | Promise<File | undefined>;
114
251
  /** Token budget for the meter. */
115
252
  budgetTokens: number;
116
253
  /** Slash-commands. Names must start with `/`. */
@@ -203,6 +340,6 @@ interface PromptInputProps extends Omit<HTMLAttributes<HTMLDivElement>, "onSubmi
203
340
  /** Fraction of `budgetTokens` past which a `"near-limit"` meter takes the warning colour. Default 0.9. */
204
341
  meterWarnAt?: number;
205
342
  }
206
- declare function PromptInput({ budgetTokens, commands, mentions, showHint, platform, onSubmit, placeholder, charsPerToken, mentionColor, maxHeight, aboveInput, className, pasteThreshold, pasteImages, previewChars, renderPastePreview, pasteClassNames, pasteFilesName, attachButton, sendButton, usedTokens, meter, meterShowAt, meterWarnAt, ...rest }: PromptInputProps): react.JSX.Element;
343
+ declare const PromptInput: react.ForwardRefExoticComponent<PromptInputProps & react.RefAttributes<PromptInputHandle>>;
207
344
 
208
- export { type FancyPlatform as F, type PlatformHints as P, type PromptAttachment as a, type PromptCmd as b, PromptInput as c, type PromptInputProps as d, type PromptMention as e, browserPlatformHints as f, detectPlatform as g, modifierKeyLabel as m };
345
+ export { type FancyPlatform as F, type PlatformHints as P, type PromptAttachment as a, type PromptCmd as b, type PromptDraft as c, type PromptDraftFile as d, type PromptDraftPill as e, PromptInput as f, type PromptInputHandle as g, type PromptInputProps as h, type PromptMention as i, type PromptSetDraftOptions as j, browserPlatformHints as k, detectPlatform as l, modifierKeyLabel as m, promptFileToDataUrl as n, promptDataUrlToFile as p };
package/dist/index.cjs CHANGED
@@ -36,7 +36,7 @@ var chunkEZA6GWZL_cjs = require('./chunk-EZA6GWZL.cjs');
36
36
  var chunkKSU6RYWU_cjs = require('./chunk-KSU6RYWU.cjs');
37
37
  var chunkK5MQZTDW_cjs = require('./chunk-K5MQZTDW.cjs');
38
38
  var chunkB4YV2V7G_cjs = require('./chunk-B4YV2V7G.cjs');
39
- var chunkZ6LYBL5L_cjs = require('./chunk-Z6LYBL5L.cjs');
39
+ var chunkC463C4MM_cjs = require('./chunk-C463C4MM.cjs');
40
40
  var chunkOASSZI6M_cjs = require('./chunk-OASSZI6M.cjs');
41
41
  require('./chunk-I27K6NLX.cjs');
42
42
  var chunkAQ4DZT4Y_cjs = require('./chunk-AQ4DZT4Y.cjs');
@@ -77,7 +77,7 @@ var chunkFQSSVOVQ_cjs = require('./chunk-FQSSVOVQ.cjs');
77
77
  require('./chunk-S3TRSECD.cjs');
78
78
  var chunkZ7SQMQTF_cjs = require('./chunk-Z7SQMQTF.cjs');
79
79
  var chunkAJPYGRLA_cjs = require('./chunk-AJPYGRLA.cjs');
80
- var chunkA6HWKQ4D_cjs = require('./chunk-A6HWKQ4D.cjs');
80
+ var chunkXZOBGKZL_cjs = require('./chunk-XZOBGKZL.cjs');
81
81
  var chunkATIDUWY6_cjs = require('./chunk-ATIDUWY6.cjs');
82
82
  var chunkA2JUTNE6_cjs = require('./chunk-A2JUTNE6.cjs');
83
83
  var chunkT6YELZPJ_cjs = require('./chunk-T6YELZPJ.cjs');
@@ -373,19 +373,27 @@ Object.defineProperty(exports, "Progress", {
373
373
  });
374
374
  Object.defineProperty(exports, "PromptInput", {
375
375
  enumerable: true,
376
- get: function () { return chunkZ6LYBL5L_cjs.PromptInput; }
376
+ get: function () { return chunkC463C4MM_cjs.PromptInput; }
377
377
  });
378
378
  Object.defineProperty(exports, "browserPlatformHints", {
379
379
  enumerable: true,
380
- get: function () { return chunkZ6LYBL5L_cjs.browserPlatformHints; }
380
+ get: function () { return chunkC463C4MM_cjs.browserPlatformHints; }
381
381
  });
382
382
  Object.defineProperty(exports, "detectPlatform", {
383
383
  enumerable: true,
384
- get: function () { return chunkZ6LYBL5L_cjs.detectPlatform; }
384
+ get: function () { return chunkC463C4MM_cjs.detectPlatform; }
385
385
  });
386
386
  Object.defineProperty(exports, "modifierKeyLabel", {
387
387
  enumerable: true,
388
- get: function () { return chunkZ6LYBL5L_cjs.modifierKeyLabel; }
388
+ get: function () { return chunkC463C4MM_cjs.modifierKeyLabel; }
389
+ });
390
+ Object.defineProperty(exports, "promptDataUrlToFile", {
391
+ enumerable: true,
392
+ get: function () { return chunkC463C4MM_cjs.promptDataUrlToFile; }
393
+ });
394
+ Object.defineProperty(exports, "promptFileToDataUrl", {
395
+ enumerable: true,
396
+ get: function () { return chunkC463C4MM_cjs.promptFileToDataUrl; }
389
397
  });
390
398
  Object.defineProperty(exports, "Tooltip", {
391
399
  enumerable: true,
@@ -637,11 +645,11 @@ Object.defineProperty(exports, "useDrawer", {
637
645
  });
638
646
  Object.defineProperty(exports, "Dropdown", {
639
647
  enumerable: true,
640
- get: function () { return chunkA6HWKQ4D_cjs.Dropdown; }
648
+ get: function () { return chunkXZOBGKZL_cjs.Dropdown; }
641
649
  });
642
650
  Object.defineProperty(exports, "useDropdown", {
643
651
  enumerable: true,
644
- get: function () { return chunkA6HWKQ4D_cjs.useDropdown; }
652
+ get: function () { return chunkXZOBGKZL_cjs.useDropdown; }
645
653
  });
646
654
  Object.defineProperty(exports, "Editor", {
647
655
  enumerable: true,
package/dist/index.d.cts CHANGED
@@ -87,7 +87,7 @@ export { Kanban, KanbanCardMoveHandler, KanbanCardProps, KanbanColumnHandleProps
87
87
  export { DropPosition, TreeNav, TreeNavContextValue, TreeNavProps, TreeNodeData, TreeNodeProps, useTreeNav } from './tree-nav.cjs';
88
88
  export { FileBrowser, FileBrowserContextValue, FileBrowserNewFolder, FileBrowserNodeProps, FileBrowserPathBarProps, FileBrowserProps, FileBrowserProvider, FileBrowserRow, FileBrowserToolbarProps, FileBrowserTreeProps, FileEntry, FileKind, FileLoadStatus, FileSelectMode, FileSnapshotNode, FileSort, FileSortDirection, FileSortField, useFileBrowser, validateFolderName } from './file-browser.cjs';
89
89
  export { M as MediaKind, a as MediaViewer, b as MediaViewerProps, R as ResolveMediaTypeInput, r as resolveMediaType } from './index-WG8UdfiE.cjs';
90
- export { F as FancyPlatform, P as PlatformHints, a as PromptAttachment, b as PromptCmd, c as PromptInput, d as PromptInputProps, e as PromptMention, f as browserPlatformHints, g as detectPlatform, m as modifierKeyLabel } from './index-BF5qsQur.cjs';
90
+ export { F as FancyPlatform, P as PlatformHints, a as PromptAttachment, b as PromptCmd, c as PromptDraft, d as PromptDraftFile, e as PromptDraftPill, f as PromptInput, g as PromptInputHandle, h as PromptInputProps, i as PromptMention, j as PromptSetDraftOptions, k as browserPlatformHints, l as detectPlatform, m as modifierKeyLabel, p as promptDataUrlToFile, n as promptFileToDataUrl } from './index-CCJVT9Hu.cjs';
91
91
  import { P as Placement } from './types-DJI492iY.cjs';
92
92
  export { A as ActionColor, B as ButtonColor, C as Color, S as Size, V as Variant } from './types-DJI492iY.cjs';
93
93
  import { RefObject } from 'react';
package/dist/index.d.ts CHANGED
@@ -87,7 +87,7 @@ export { Kanban, KanbanCardMoveHandler, KanbanCardProps, KanbanColumnHandleProps
87
87
  export { DropPosition, TreeNav, TreeNavContextValue, TreeNavProps, TreeNodeData, TreeNodeProps, useTreeNav } from './tree-nav.js';
88
88
  export { FileBrowser, FileBrowserContextValue, FileBrowserNewFolder, FileBrowserNodeProps, FileBrowserPathBarProps, FileBrowserProps, FileBrowserProvider, FileBrowserRow, FileBrowserToolbarProps, FileBrowserTreeProps, FileEntry, FileKind, FileLoadStatus, FileSelectMode, FileSnapshotNode, FileSort, FileSortDirection, FileSortField, useFileBrowser, validateFolderName } from './file-browser.js';
89
89
  export { M as MediaKind, a as MediaViewer, b as MediaViewerProps, R as ResolveMediaTypeInput, r as resolveMediaType } from './index-BiyyxWtb.js';
90
- export { F as FancyPlatform, P as PlatformHints, a as PromptAttachment, b as PromptCmd, c as PromptInput, d as PromptInputProps, e as PromptMention, f as browserPlatformHints, g as detectPlatform, m as modifierKeyLabel } from './index-BZON-gXr.js';
90
+ export { F as FancyPlatform, P as PlatformHints, a as PromptAttachment, b as PromptCmd, c as PromptDraft, d as PromptDraftFile, e as PromptDraftPill, f as PromptInput, g as PromptInputHandle, h as PromptInputProps, i as PromptMention, j as PromptSetDraftOptions, k as browserPlatformHints, l as detectPlatform, m as modifierKeyLabel, p as promptDataUrlToFile, n as promptFileToDataUrl } from './index-DRlltbdV.js';
91
91
  import { P as Placement } from './types-DJI492iY.js';
92
92
  export { A as ActionColor, B as ButtonColor, C as Color, S as Size, V as Variant } from './types-DJI492iY.js';
93
93
  import { RefObject } from 'react';
package/dist/index.js CHANGED
@@ -34,7 +34,7 @@ export { Sidebar, useSidebar } from './chunk-YJHP5FVA.js';
34
34
  export { Pillbox } from './chunk-G35XN5WV.js';
35
35
  export { Profile } from './chunk-GOE36IXL.js';
36
36
  export { Progress } from './chunk-UE3QO7R3.js';
37
- export { PromptInput, browserPlatformHints, detectPlatform, modifierKeyLabel } from './chunk-BTNF3QBR.js';
37
+ export { PromptInput, browserPlatformHints, detectPlatform, modifierKeyLabel, promptDataUrlToFile, promptFileToDataUrl } from './chunk-COXHFZR6.js';
38
38
  export { Tooltip } from './chunk-GR2WKBER.js';
39
39
  import './chunk-SV5BW3IA.js';
40
40
  export { MediaViewer, resolveMediaType } from './chunk-5PHPJL7Z.js';
@@ -75,7 +75,7 @@ export { Container } from './chunk-VKMPVFQK.js';
75
75
  import './chunk-AUTW5O4W.js';
76
76
  export { ContextMenu, useContextMenu } from './chunk-NSJBVV7R.js';
77
77
  export { Drawer, useDrawer } from './chunk-PXZRYL5I.js';
78
- export { Dropdown, useDropdown } from './chunk-JG2DJK5A.js';
78
+ export { Dropdown, useDropdown } from './chunk-BXPLERIJ.js';
79
79
  export { Editor, useEditor } from './chunk-CAUEKGMR.js';
80
80
  export { THEME_STORAGE_KEY, getThemePreference, initTheme, resolveTheme, setThemePreference, subscribeTheme, useId, useNodeRegistry, useTheme } from './chunk-2BXMMVQS.js';
81
81
  export { usePanZoom } from './chunk-CPY7NHXM.js';
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunkZ6LYBL5L_cjs = require('./chunk-Z6LYBL5L.cjs');
3
+ var chunkC463C4MM_cjs = require('./chunk-C463C4MM.cjs');
4
4
  require('./chunk-OASSZI6M.cjs');
5
5
  require('./chunk-I27K6NLX.cjs');
6
6
  require('./chunk-AIA5UNQ5.cjs');
@@ -23,7 +23,15 @@ require('./chunk-ZU5Q77OT.cjs');
23
23
 
24
24
  Object.defineProperty(exports, "PromptInput", {
25
25
  enumerable: true,
26
- get: function () { return chunkZ6LYBL5L_cjs.PromptInput; }
26
+ get: function () { return chunkC463C4MM_cjs.PromptInput; }
27
+ });
28
+ Object.defineProperty(exports, "promptDataUrlToFile", {
29
+ enumerable: true,
30
+ get: function () { return chunkC463C4MM_cjs.promptDataUrlToFile; }
31
+ });
32
+ Object.defineProperty(exports, "promptFileToDataUrl", {
33
+ enumerable: true,
34
+ get: function () { return chunkC463C4MM_cjs.promptFileToDataUrl; }
27
35
  });
28
36
  //# sourceMappingURL=prompt-input.cjs.map
29
37
  //# sourceMappingURL=prompt-input.cjs.map
@@ -1,3 +1,3 @@
1
- export { a as PromptAttachment, b as PromptCmd, c as PromptInput, d as PromptInputProps, e as PromptMention } from './index-BF5qsQur.cjs';
1
+ export { a as PromptAttachment, b as PromptCmd, c as PromptDraft, d as PromptDraftFile, e as PromptDraftPill, f as PromptInput, g as PromptInputHandle, h as PromptInputProps, i as PromptMention, j as PromptSetDraftOptions, p as promptDataUrlToFile, n as promptFileToDataUrl } from './index-CCJVT9Hu.cjs';
2
2
  import 'react';
3
3
  import './PastePills.types-CDyotkUb.cjs';
@@ -1,3 +1,3 @@
1
- export { a as PromptAttachment, b as PromptCmd, c as PromptInput, d as PromptInputProps, e as PromptMention } from './index-BZON-gXr.js';
1
+ export { a as PromptAttachment, b as PromptCmd, c as PromptDraft, d as PromptDraftFile, e as PromptDraftPill, f as PromptInput, g as PromptInputHandle, h as PromptInputProps, i as PromptMention, j as PromptSetDraftOptions, p as promptDataUrlToFile, n as promptFileToDataUrl } from './index-DRlltbdV.js';
2
2
  import 'react';
3
3
  import './PastePills.types-CDyotkUb.js';
@@ -1,4 +1,4 @@
1
- export { PromptInput } from './chunk-BTNF3QBR.js';
1
+ export { PromptInput, promptDataUrlToFile, promptFileToDataUrl } from './chunk-COXHFZR6.js';
2
2
  import './chunk-GR2WKBER.js';
3
3
  import './chunk-SV5BW3IA.js';
4
4
  import './chunk-TJ66S5K6.js';
@@ -52,14 +52,91 @@ import { PromptInput } from "@particle-academy/react-fancy";
52
52
  | maxHeight | `number` | `280` | Max textarea height in pixels. |
53
53
  | aboveInput | `ReactNode` | — | Rendered inside the rounded shell, above the textarea. Use this for a drawer of tools/files/prompts/etc. so the drawer and composer share one visual panel — see [ChatDrawer](./ChatDrawer.md). Added in `3.2.0`. |
54
54
 
55
+ ### Controlled state and drafts
56
+
57
+ | Prop | Type | Default | Description |
58
+ |------|------|---------|-------------|
59
+ | value | `string` | — | The text, controlled. With paste pills on, it carries a `pasteMarker(id)` per pill. |
60
+ | defaultValue | `string` | `""` | The starting text, uncontrolled. |
61
+ | onChange | `(text) => void` | — | Called when the person edits the text, and with `""` after a send. Not called by `setDraft`. |
62
+ | draft | `PromptDraft` | — | The whole draft (text, pills, attachments), controlled. Takes precedence over `value`. |
63
+ | defaultDraft | `PromptDraft` | — | The starting draft, uncontrolled. Takes precedence over `defaultValue`. |
64
+ | onDraftChange | `(draft) => void` | — | Called on every change the person makes, and when a `persistAttachment` handle arrives. Not called by `setDraft`. |
65
+ | persistAttachment | `(file, entry) => string \| undefined \| Promise<…>` | — | Stores an attached or pasted file and returns a handle. The handle becomes `entry.ref`. |
66
+ | restoreAttachment | `(entry) => File \| undefined \| Promise<…>` | — | Gets the File back for a restored entry. |
67
+
68
+ ## Saving and restoring a draft
69
+
70
+ The draft is plain JSON, so a host can store it anywhere:
71
+
72
+ ```tsx
73
+ const composer = useRef<PromptInputHandle>(null);
74
+
75
+ // save, e.g. on onDraftChange or before quitting
76
+ localStorage.setItem(`draft:${chatId}`, JSON.stringify(composer.current!.getDraft()));
77
+
78
+ // restore after a restart. This does not focus the composer or move any caret.
79
+ const saved = localStorage.getItem(`draft:${chatId}`);
80
+ if (saved) composer.current!.setDraft(JSON.parse(saved));
81
+
82
+ <PromptInput ref={composer} budgetTokens={32000} pasteThreshold={500} pasteImages onSubmit={send} />
83
+ ```
84
+
85
+ - `setDraft(draft, { focus?: false })` does **not** focus unless you pass `{ focus: true }`. It never moves the caret of whatever does have focus.
86
+ - `setDraft` does not call `onChange` / `onDraftChange`. You set the draft yourself, so you already have it.
87
+ - A draft with an unknown `v` is refused with a console warning.
88
+
89
+ ### Attachments: where the bytes go
90
+
91
+ A `File` cannot be stored as JSON, and by default a draft does not carry the
92
+ bytes. Base64 in every draft would make each save as large as everything
93
+ attached, which matters for a host that saves on every keystroke. Each file in
94
+ a draft is `{ id, name, type, bytes, ref? }`. You decide where the bytes live:
95
+
96
+ ```tsx
97
+ // Your own store (IndexedDB, upload, disk): return a handle, async is fine.
98
+ <PromptInput
99
+ persistAttachment={async (file, entry) => { await idb.put(entry.id, file); return entry.id; }}
100
+ restoreAttachment={(entry) => (entry.ref ? idb.get(entry.ref) : undefined)}
101
+ …
102
+ />
103
+
104
+ // Or keep the bytes in the draft itself, simple for small files:
105
+ <PromptInput
106
+ persistAttachment={promptFileToDataUrl}
107
+ restoreAttachment={(f) => (f.ref ? promptDataUrlToFile(f.ref, f.name, f.type) : undefined)}
108
+ …
109
+ />
110
+ ```
111
+
112
+ When there is no File for a restored entry, its chip or pill still shows. It
113
+ reaches `onSubmit` with its `ref` and `file: undefined`, and the host sends it
114
+ from its own store.
115
+
55
116
  ## Types
56
117
 
57
118
  ```ts
58
119
  type PromptCmd = { name: string; hint: string };
59
120
  type PromptMention = { id: string; name: string; kind: string };
60
- type PromptAttachment = { id: string; name: string; bytes: number };
121
+ type PromptAttachment = { id: string; name: string; bytes: number; file?: File; type?: string; ref?: string };
122
+
123
+ interface PromptDraftFile { id: string; name: string; type: string; bytes: number; ref?: string }
124
+ type PromptDraftPill =
125
+ | { kind: "text"; id: string; text: string }
126
+ | { kind: "image"; id: string; label: string; file: PromptDraftFile };
127
+ interface PromptDraft { v: 1; text: string; pills: PromptDraftPill[]; attachments: PromptDraftFile[] }
128
+
129
+ interface PromptInputHandle {
130
+ getDraft(): PromptDraft;
131
+ setDraft(draft: PromptDraft, options?: { focus?: boolean }): void;
132
+ focus(): void;
133
+ }
61
134
  ```
62
135
 
136
+ Data handles: the root `[data-react-fancy-prompt-input]`, each attachment chip
137
+ `[data-react-fancy-prompt-input-attachment="<id>"]`, plus `-attach`, `-send`,
138
+ `-meter` and `-hint`.
139
+
63
140
  ## Defaults
64
141
 
65
142
  | Mention kind | Default colour |
@@ -42,6 +42,8 @@ const [order, setOrder] = useState(fields.map((f) => f.id));
42
42
 
43
43
  Uncontrolled: leave out `value`, optionally pass `defaultValue`, and listen to `onReorder`.
44
44
 
45
+ **The cell is already framed.** By default every cell draws its own border, surface and padding, so `renderItem` should return bare content, as above. If you wrap it in your own bordered card you get a frame inside a frame. To draw the cell yourself, pass `unstyled` (drag, keyboard and announcements are unchanged), or adjust the default frame with `cellClassName`.
46
+
45
47
  ## Order: `value` is a list of ids
46
48
 
47
49
  `items` carries the data and `value` (or `defaultValue`) carries the order. The order is not taken from `items`. This follows the kit's controlled pattern (`value` / `defaultValue`, see `useControllableState`), and it suits the two consumers:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@particle-academy/react-fancy",
3
- "version": "5.64.0",
3
+ "version": "5.65.0",
4
4
  "description": "React UI component library for Human+ UX — controlled, agent-bridgeable primitives",
5
5
  "repository": {
6
6
  "type": "git",