@particle-academy/react-fancy 5.70.0 → 5.70.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/dist/{chunk-J7S42MJP.js → chunk-FYUEDPST.js} +39 -20
- package/dist/chunk-FYUEDPST.js.map +1 -0
- package/dist/{chunk-HN5J7TK6.js → chunk-GDH43QH6.js} +21 -8
- package/dist/chunk-GDH43QH6.js.map +1 -0
- package/dist/{chunk-AC3W3TIV.js → chunk-J6IQHJET.js} +14 -5
- package/dist/chunk-J6IQHJET.js.map +1 -0
- package/dist/{chunk-XNHAJQRF.cjs → chunk-JKD6CFIP.cjs} +14 -5
- package/dist/chunk-JKD6CFIP.cjs.map +1 -0
- package/dist/{chunk-LBSAWRFT.cjs → chunk-NPSKZ7ZB.cjs} +22 -9
- package/dist/chunk-NPSKZ7ZB.cjs.map +1 -0
- package/dist/{chunk-XAKZ6VBP.cjs → chunk-PS7C6GKI.cjs} +40 -21
- package/dist/chunk-PS7C6GKI.cjs.map +1 -0
- package/dist/composer.cjs +3 -3
- package/dist/composer.d.cts +3 -2
- package/dist/composer.d.ts +3 -2
- package/dist/composer.js +2 -2
- package/dist/{index-BAtF94lw.d.ts → index-B8OkuYSt.d.ts} +3 -2
- package/dist/{index-xwL2MACB.d.cts → index-BMVQnKTH.d.cts} +3 -2
- package/dist/index.cjs +10 -10
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -3
- package/dist/prompt-input.cjs +5 -5
- package/dist/prompt-input.d.cts +1 -1
- package/dist/prompt-input.d.ts +1 -1
- package/dist/prompt-input.js +2 -2
- package/docs/Composer.md +5 -2
- package/docs/PromptInput.md +8 -2
- package/package.json +1 -1
- package/dist/chunk-AC3W3TIV.js.map +0 -1
- package/dist/chunk-HN5J7TK6.js.map +0 -1
- package/dist/chunk-J7S42MJP.js.map +0 -1
- package/dist/chunk-LBSAWRFT.cjs.map +0 -1
- package/dist/chunk-XAKZ6VBP.cjs.map +0 -1
- package/dist/chunk-XNHAJQRF.cjs.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
+
## [5.70.1] - 2026-10-27
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- **An acknowledged send no longer clears a newer draft that happens to read
|
|
19
|
+
the same** (`Composer`, `PromptInput`, from 5.70.0). When an async `onSubmit`
|
|
20
|
+
resolved, the draft was cleared if its text and the ids of its pills and
|
|
21
|
+
attachments still matched what was sent. A newer draft that compared equal was
|
|
22
|
+
therefore thrown away: an edit typed and then deleted during the send, a host
|
|
23
|
+
`setDraft` that restored the same draft, or a pill's text or an attachment
|
|
24
|
+
changed under the same id. The clear is now tied to the draft revision that
|
|
25
|
+
was sent. Anything that changes the draft after the send (the person, the
|
|
26
|
+
host's `setDraft`, a controlled `draft` / `value` / `heldPastes` /
|
|
27
|
+
`heldImages` that now says something different) makes it newer, and a newer
|
|
28
|
+
draft is kept whole. A `ref` arriving from `persistAttachment` mid-send is not
|
|
29
|
+
a change, so it does not block the clear. A controlled host that echoes a
|
|
30
|
+
copy of each draft is still cleared, because an echo that says the same thing
|
|
31
|
+
is not a new revision. Nothing to do on upgrade.
|
|
32
|
+
- **Only the newest send can acknowledge.** Both components refuse a second
|
|
33
|
+
send while one is pending, and nothing turns that off. If two ever overlapped,
|
|
34
|
+
the older one settling would have cleared the draft and ended the newer one's
|
|
35
|
+
`submitting` state. It is now ignored, except that its failure still reaches
|
|
36
|
+
`onSubmitError`.
|
|
37
|
+
|
|
14
38
|
## [5.70.0] - 2026-10-27
|
|
15
39
|
|
|
16
40
|
### Added
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Tooltip } from './chunk-GR2WKBER.js';
|
|
2
|
-
import { useAcknowledgedSubmit } from './chunk-
|
|
2
|
+
import { useAcknowledgedSubmit } from './chunk-J6IQHJET.js';
|
|
3
3
|
import { useInlinePaste, InlinePasteEditor, PastedFilesInput, markedIds, expandMarkers } from './chunk-6PRXG7L6.js';
|
|
4
4
|
import { visuallyHidden } from './chunk-I3LAFKJU.js';
|
|
5
5
|
import { isImeComposing } from './chunk-S42OGG43.js';
|
|
@@ -177,6 +177,12 @@ var PromptInput = forwardRef(function PromptInput2({
|
|
|
177
177
|
const modelRef = useRef(model);
|
|
178
178
|
modelRef.current = model;
|
|
179
179
|
const text = model.text;
|
|
180
|
+
const revisionRef = useRef(0);
|
|
181
|
+
const seenRef = useRef(model);
|
|
182
|
+
if (seenRef.current !== model) {
|
|
183
|
+
if (!sameDraftContent(seenRef.current, model)) revisionRef.current += 1;
|
|
184
|
+
seenRef.current = model;
|
|
185
|
+
}
|
|
180
186
|
const onChangeRef = useRef(onChange);
|
|
181
187
|
onChangeRef.current = onChange;
|
|
182
188
|
const onDraftChangeRef = useRef(onDraftChange);
|
|
@@ -184,11 +190,13 @@ var PromptInput = forwardRef(function PromptInput2({
|
|
|
184
190
|
const persistRef = useRef(persistAttachment);
|
|
185
191
|
persistRef.current = persistAttachment;
|
|
186
192
|
const commit = useCallback(
|
|
187
|
-
(update, notify = true) => {
|
|
193
|
+
(update, notify = true, revise = true) => {
|
|
188
194
|
const prev = modelRef.current;
|
|
189
195
|
const next = update(prev);
|
|
190
196
|
if (next === prev) return;
|
|
197
|
+
if (revise) revisionRef.current += 1;
|
|
191
198
|
modelRef.current = next;
|
|
199
|
+
seenRef.current = next;
|
|
192
200
|
setInnerDraft(next);
|
|
193
201
|
if (!notify) return;
|
|
194
202
|
if (next.text !== prev.text) onChangeRef.current?.(next.text);
|
|
@@ -201,15 +209,19 @@ var PromptInput = forwardRef(function PromptInput2({
|
|
|
201
209
|
const requestedRef = useRef(/* @__PURE__ */ new Set());
|
|
202
210
|
const [filesVersion, bumpFiles] = useReducer((n) => n + 1, 0);
|
|
203
211
|
const setFileRef = useCallback(
|
|
204
|
-
(id, fileRef2) => commit(
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
212
|
+
(id, fileRef2) => commit(
|
|
213
|
+
(m) => {
|
|
214
|
+
const withRef = (f) => f.id === id ? { ...f, ref: fileRef2 } : f;
|
|
215
|
+
if (!promptDraftFiles(m).some((f) => f.id === id)) return m;
|
|
216
|
+
return {
|
|
217
|
+
...m,
|
|
218
|
+
attachments: m.attachments.map(withRef),
|
|
219
|
+
pills: m.pills.map((p) => p.kind === "image" ? { ...p, file: withRef(p.file) } : p)
|
|
220
|
+
};
|
|
221
|
+
},
|
|
222
|
+
true,
|
|
223
|
+
false
|
|
224
|
+
),
|
|
213
225
|
[commit]
|
|
214
226
|
);
|
|
215
227
|
const persist = useCallback(
|
|
@@ -475,9 +487,9 @@ var PromptInput = forwardRef(function PromptInput2({
|
|
|
475
487
|
result = onSubmit(text, attachments);
|
|
476
488
|
}
|
|
477
489
|
setPicker(null);
|
|
478
|
-
const sent =
|
|
490
|
+
const sent = revisionRef.current;
|
|
479
491
|
ack.track(result, () => {
|
|
480
|
-
if (
|
|
492
|
+
if (revisionRef.current !== sent) return;
|
|
481
493
|
commit(() => EMPTY_DRAFT);
|
|
482
494
|
filesRef.current.clear();
|
|
483
495
|
placeholdersRef.current.clear();
|
|
@@ -855,12 +867,19 @@ var PromptInput = forwardRef(function PromptInput2({
|
|
|
855
867
|
}
|
|
856
868
|
);
|
|
857
869
|
});
|
|
858
|
-
function
|
|
859
|
-
return
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
870
|
+
function sameDraftContent(a, b) {
|
|
871
|
+
if (a.text !== b.text) return false;
|
|
872
|
+
if (a.pills === b.pills && a.attachments === b.attachments) return true;
|
|
873
|
+
const key = (d) => JSON.stringify([
|
|
874
|
+
d.pills.map(
|
|
875
|
+
(p) => p.kind === "text" ? [p.kind, p.id, p.text, p.label] : [p.kind, p.id, p.label, fileKey(p.file)]
|
|
876
|
+
),
|
|
877
|
+
d.attachments.map(fileKey)
|
|
863
878
|
]);
|
|
879
|
+
return key(a) === key(b);
|
|
880
|
+
}
|
|
881
|
+
function fileKey(f) {
|
|
882
|
+
return [f.id, f.name, f.type, f.bytes];
|
|
864
883
|
}
|
|
865
884
|
function fmtTokens(n) {
|
|
866
885
|
if (n < 1e3) return `${n}`;
|
|
@@ -873,5 +892,5 @@ function fmtSize(b) {
|
|
|
873
892
|
}
|
|
874
893
|
|
|
875
894
|
export { PromptInput, browserPlatformHints, detectPlatform, modifierKeyLabel, promptDataUrlToFile, promptFileToDataUrl };
|
|
876
|
-
//# sourceMappingURL=chunk-
|
|
877
|
-
//# sourceMappingURL=chunk-
|
|
895
|
+
//# sourceMappingURL=chunk-FYUEDPST.js.map
|
|
896
|
+
//# sourceMappingURL=chunk-FYUEDPST.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/PromptInput/prompt-draft.ts","../src/utils/platform.ts","../src/components/PromptInput/PromptInput.tsx"],"names":["PromptInput","fileRef"],"mappings":"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","file":"chunk-FYUEDPST.js","sourcesContent":["import { expandMarkers, markedIds, type HeldImage } from \"../PastePills/paste-markers\";\n\n/**\n * A file in a saved draft — an attachment chip, or the image behind an image\n * pill — described by data alone, so the draft survives `JSON.stringify`.\n *\n * The bytes are deliberately NOT in here. A `File` does not survive JSON, and\n * putting base64 in the draft by default would make every saved draft as large\n * as everything attached to it: a 6 MB screenshot becomes 8 MB of string written\n * on every keystroke by a host that saves on change. Instead the host decides\n * where bytes live, through `persistAttachment` (save) and `restoreAttachment`\n * (load), and the draft carries only the host's handle for them in `ref`.\n *\n * A host that WANTS the bytes inline passes `promptFileToDataUrl` as\n * `persistAttachment`, and `ref` is then a `data:` URL.\n */\nexport interface PromptDraftFile {\n /** Stable for the life of the draft. For an image pill, the pill's own id. */\n id: string;\n name: string;\n /** MIME type, `\"\"` when the browser did not know it. */\n type: string;\n bytes: number;\n /**\n * The host's handle for the stored bytes — whatever `persistAttachment`\n * returned: a storage key, an upload URL, a `data:` URL. Absent when the host\n * keeps nothing, in which case a restored attachment has no `File`.\n */\n ref?: string;\n}\n\n/** A paste held as a pill inside the message. Its marker sits in `text`. */\nexport type PromptDraftPill =\n | {\n kind: \"text\";\n id: string;\n text: string;\n /**\n * Shown on the pill in place of `Pasted text · N characters`. Display only:\n * sending still expands the pill to the full `text`. Plain text, flattened\n * to one line and capped at 80 characters when drawn. Optional.\n */\n label?: string;\n }\n | { kind: \"image\"; id: string; label: string; file: PromptDraftFile };\n\n/**\n * Everything a PromptInput holds, as JSON. Versioned, so a draft saved by one\n * release and read by a later one is recognised or refused, never guessed at.\n *\n * `text` is the raw value: with paste pills on, each pill is a marker in it\n * (`pasteMarker(id)`), because the message itself is the record of what is\n * attached and where. Mentions and slash commands are plain text in it.\n */\nexport interface PromptDraft {\n v: 1;\n text: string;\n pills: PromptDraftPill[];\n attachments: PromptDraftFile[];\n}\n\nexport interface PromptSetDraftOptions {\n /**\n * Focus the composer after restoring, caret at the end. Default `false`:\n * restoring a draft never moves focus or anybody's caret on its own\n * (genie#1235 was exactly that).\n */\n focus?: boolean;\n /**\n * With `focus: true`: whether the browser may scroll to reveal the composer.\n * Default `true` (no scrolling): a bare `el.focus()` scrolls EVERY scrollable\n * ancestor up to the document, which scrolled a whole host window\n * (genie#1290). Pass `false` to let it scroll.\n */\n preventScroll?: boolean;\n}\n\n/** Options for `PromptInputHandle.focus()`. Mirrors the DOM's `FocusOptions`. */\nexport interface PromptFocusOptions {\n /**\n * Default `true`: focusing never scrolls the page or any ancestor\n * (genie#1290). Pass `false` to let the browser scroll the composer into view.\n */\n preventScroll?: boolean;\n}\n\n/** The imperative handle a `ref` on `PromptInput` receives. */\nexport interface PromptInputHandle {\n /** The current draft. A fresh JSON-safe copy; mutating it changes nothing. */\n getDraft(): PromptDraft;\n /**\n * Replace the whole draft. Does NOT call `onChange` / `onDraftChange` (the\n * host is the one setting it), does NOT focus unless `{ focus: true }`.\n * A draft whose `v` this release does not know is refused with a warning.\n */\n setDraft(draft: PromptDraft, options?: PromptSetDraftOptions): void;\n /**\n * Focus the composer, caret at the end. Does not scroll anything unless\n * `{ preventScroll: false }`.\n */\n focus(options?: PromptFocusOptions): void;\n}\n\nexport const EMPTY_DRAFT: PromptDraft = { v: 1, text: \"\", pills: [], attachments: [] };\n\nfunction readFile(raw: unknown, id?: string): PromptDraftFile | null {\n if (!raw || typeof raw !== \"object\") return null;\n const f = raw as Record<string, unknown>;\n const fileId = id ?? f.id;\n if (typeof fileId !== \"string\" || typeof f.name !== \"string\") return null;\n const out: PromptDraftFile = {\n id: fileId,\n name: f.name,\n type: typeof f.type === \"string\" ? f.type : \"\",\n bytes: typeof f.bytes === \"number\" && Number.isFinite(f.bytes) ? f.bytes : 0,\n };\n if (typeof f.ref === \"string\") out.ref = f.ref;\n return out;\n}\n\nfunction readPill(raw: unknown): PromptDraftPill | null {\n if (!raw || typeof raw !== \"object\") return null;\n const p = raw as Record<string, unknown>;\n if (typeof p.id !== \"string\") return null;\n if (p.kind === \"text\" && typeof p.text === \"string\") {\n return typeof p.label === \"string\"\n ? { kind: \"text\", id: p.id, text: p.text, label: p.label }\n : { kind: \"text\", id: p.id, text: p.text };\n }\n if (p.kind === \"image\" && typeof p.label === \"string\") {\n const file = readFile(p.file, p.id);\n return file ? { kind: \"image\", id: p.id, label: p.label, file } : null;\n }\n return null;\n}\n\n/**\n * Validate a draft from storage. `null` for anything that is not a v1 draft.\n *\n * `inline` is whether the composer can show pills. Without them (no\n * `pasteThreshold` / `pasteImages`) a pill would surface as raw marker\n * characters, so text pills are expanded in place and image pills become `[label]`\n * in the text plus an ordinary attachment — what the message would have sent.\n */\nexport function readPromptDraft(raw: unknown, inline: boolean): PromptDraft | null {\n if (!raw || typeof raw !== \"object\" || (raw as { v?: unknown }).v !== 1) return null;\n const d = raw as Record<string, unknown>;\n\n let text = typeof d.text === \"string\" ? d.text : \"\";\n let pills = (Array.isArray(d.pills) ? d.pills : [])\n .map(readPill)\n .filter((p): p is PromptDraftPill => p !== null);\n let attachments = (Array.isArray(d.attachments) ? d.attachments : [])\n .map((a) => readFile(a))\n .filter((a): a is PromptDraftFile => a !== null);\n\n // The message is the record: a pill the text no longer marks is not attached.\n const marked = new Set(markedIds(text));\n pills = pills.filter((p) => marked.has(p.id));\n\n if (!inline && pills.length > 0) {\n const texts = pills.flatMap((p) => (p.kind === \"text\" ? [{ id: p.id, text: p.text }] : []));\n const images = pills.flatMap((p) => (p.kind === \"image\" ? [p] : []));\n text = expandMarkers(\n text,\n texts,\n images.map((p) => ({ id: p.id, label: p.label }) as HeldImage),\n );\n attachments = [...attachments, ...images.map((p) => p.file)];\n pills = [];\n }\n\n return { v: 1, text, pills, attachments };\n}\n\n/** A deep, JSON-safe copy, so a host cannot mutate the component's state through it. */\nexport function clonePromptDraft(draft: PromptDraft): PromptDraft {\n return JSON.parse(JSON.stringify(draft)) as PromptDraft;\n}\n\n/** Every file a draft refers to: attachments, then image pills. */\nexport function promptDraftFiles(draft: PromptDraft): PromptDraftFile[] {\n return [\n ...draft.attachments,\n ...draft.pills.flatMap((p) => (p.kind === \"image\" ? [p.file] : [])),\n ];\n}\n\n/**\n * Read a File as a `data:` URL. Pass it as `persistAttachment` to keep the bytes\n * IN the draft — simple, and fine for small files; every saved draft then carries\n * every attached byte, base64-inflated by a third.\n */\nexport function promptFileToDataUrl(file: Blob): Promise<string> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = () => resolve(String(reader.result));\n reader.onerror = () => reject(reader.error);\n reader.readAsDataURL(file);\n });\n}\n\n/**\n * Turn a `data:` URL back into a File — the other half of `promptFileToDataUrl`:\n *\n * restoreAttachment={(f) => f.ref ? promptDataUrlToFile(f.ref, f.name, f.type) : undefined}\n *\n * Synchronous, so the restored draft has its Files on the first render.\n * `undefined` for anything that is not a `data:` URL.\n */\nexport function promptDataUrlToFile(dataUrl: string, name: string, type?: string): File | undefined {\n if (!dataUrl.startsWith(\"data:\")) return undefined;\n const comma = dataUrl.indexOf(\",\");\n if (comma < 0) return undefined;\n\n const meta = dataUrl.slice(5, comma);\n const body = dataUrl.slice(comma + 1);\n const mime = type || meta.split(\";\")[0] || \"application/octet-stream\";\n\n try {\n let bytes: Uint8Array<ArrayBuffer>;\n if (meta.endsWith(\";base64\")) {\n const bin = atob(body);\n bytes = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);\n } else {\n bytes = new TextEncoder().encode(decodeURIComponent(body));\n }\n return new File([bytes], name, { type: mime });\n } catch {\n return undefined;\n }\n}\n","/**\n * Which modifier key the user actually has.\n *\n * This exists because a keyboard hint is a CLAIM about the reader's hardware.\n * `⌘ + Enter to send` on Windows or Linux names a key that is not on the\n * keyboard, and a reader who cannot act on a hint learns to ignore hints — so\n * the cost is not one wrong glyph, it is the credibility of every hint beside\n * it. (Reported from a real Electron app running on Linux, where the glyph had\n * no font behind it either and rendered as tofu.)\n *\n * Two deliberate choices:\n *\n * 1. **The platform is an ARGUMENT, never a global read.** `detectPlatform`\n * takes the hints; `modifierKeyLabel` takes the verdict. Both branches are\n * therefore reachable from one machine, which is the only way a test can\n * cover the platform you are not sitting at. A function that reads\n * `navigator` internally can only ever be tested on the developer's own OS.\n * 2. **`generic` is the fallback, and that is not arbitrary.** Every Fancy\n * surface that submits on a modifier accepts `metaKey || ctrlKey`, so\n * `Ctrl` is a TRUE label on an Apple keyboard while `⌘` is a FALSE one\n * everywhere else. The honest default is the one that is pressable on all\n * of them.\n */\nexport type FancyPlatform = \"apple\" | \"generic\";\n\n/**\n * The parts of `navigator` the detection reads — nothing more, so a caller can\n * hand it a literal (a test, an Electron main process forwarding `process.platform`,\n * a server with a stored preference).\n */\nexport interface PlatformHints {\n /** `navigator.userAgentData` — the modern, non-spoofed answer where it exists. */\n userAgentData?: { platform?: string } | null;\n /** `navigator.platform` — deprecated, still the most reliable legacy signal. */\n platform?: string | null;\n /** `navigator.userAgent` — last resort. */\n userAgent?: string | null;\n}\n\n/**\n * `userAgentData.platform` is a short enumerated token (\"macOS\", \"Windows\",\n * \"Linux\", \"Android\", \"iOS\"), so it is matched exactly rather than by substring.\n */\nconst APPLE_UA_DATA_PLATFORMS = new Set([\"macos\", \"ios\", \"ipados\"]);\n\n/**\n * Legacy `platform` / `userAgent` are free text. iPadOS reports `MacIntel`, and\n * iOS user agents say \"like Mac OS X\" — both are Apple, so a Mac match is\n * correct for them too.\n */\nconst APPLE_FREE_TEXT = /\\b(mac|macintosh|iphone|ipad|ipod|darwin)/i;\n\n/** Resolve a platform from navigator-shaped hints. Unknown or absent ⇒ `generic`. */\nexport function detectPlatform(hints?: PlatformHints | null): FancyPlatform {\n if (!hints) return \"generic\";\n\n const uaData = hints.userAgentData?.platform;\n if (typeof uaData === \"string\" && uaData.length > 0) {\n // When the browser gives an enumerated answer, TRUST IT and stop. Falling\n // through to the free-text test would let a Windows UA string containing\n // the word \"Mac\" (a font name, a device model) overturn it.\n return APPLE_UA_DATA_PLATFORMS.has(uaData.trim().toLowerCase()) ? \"apple\" : \"generic\";\n }\n\n for (const value of [hints.platform, hints.userAgent]) {\n if (typeof value === \"string\" && APPLE_FREE_TEXT.test(value)) return \"apple\";\n }\n\n return \"generic\";\n}\n\n/**\n * The modifier key to NAME for a platform.\n *\n * `⌘` is emitted for `apple` and nothing else. A test enumerates the platform\n * values and counts that no other one can produce it, because a hint that names\n * the wrong key is worse than no hint.\n */\nexport function modifierKeyLabel(platform: FancyPlatform): \"⌘\" | \"Ctrl\" {\n return platform === \"apple\" ? \"⌘\" : \"Ctrl\";\n}\n\n/** SSR-safe read of the live browser hints. `null` off a browser. */\nexport function browserPlatformHints(): PlatformHints | null {\n if (typeof navigator === \"undefined\") return null;\n return navigator as PlatformHints;\n}\n","import {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useLayoutEffect,\n useMemo,\n useReducer,\n useRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { isImeComposing } from \"../../utils/ime\";\nimport { visuallyHidden } from \"../../utils/visually-hidden\";\nimport {\n useAcknowledgedSubmit,\n type SubmitResult,\n} from \"../../hooks/use-acknowledged-submit\";\nimport { Button } from \"../Button\";\nimport { Icon } from \"../Icon\";\nimport { Tooltip } from \"../Tooltip\";\nimport {\n InlinePasteEditor,\n PastedFilesInput,\n useInlinePaste,\n type ComposerPasteClassNames,\n type ComposerPastePreview,\n type InlinePasteEditorHandle,\n type HeldImage,\n type HeldPaste,\n} from \"../PastePills\";\nimport {\n EMPTY_DRAFT,\n clonePromptDraft,\n promptDraftFiles,\n readPromptDraft,\n type PromptDraft,\n type PromptDraftFile,\n type PromptDraftPill,\n type PromptInputHandle,\n type PromptSetDraftOptions,\n type PromptFocusOptions,\n} from \"./prompt-draft\";\n\nexport {\n promptDataUrlToFile,\n promptFileToDataUrl,\n} from \"./prompt-draft\";\nexport type { SubmitRejection, SubmitResult } from \"../../hooks/use-acknowledged-submit\";\nexport type {\n PromptDraft,\n PromptDraftFile,\n PromptDraftPill,\n PromptInputHandle,\n PromptSetDraftOptions,\n PromptFocusOptions,\n} from \"./prompt-draft\";\nimport {\n browserPlatformHints,\n detectPlatform,\n modifierKeyLabel,\n type FancyPlatform,\n} from \"../../utils/platform\";\n\n/**\n * PromptInput — the chat composer every AI app rebuilds. Auto-growing\n * multi-line input with:\n *\n * • slash-command picker (`/` triggers a filtered command palette,\n * ↑/↓ navigate, Enter inserts)\n * • mention picker (`@` triggers, filtered against `mentions`)\n * • drop-to-attach (drag files anywhere on the surface), paste-to-attach\n * (a pasted screenshot becomes an attachment) + chip bar\n * • submit on ⌘/Ctrl+Enter (plain Enter inserts a newline)\n * • live token-budget meter (green → amber → red as headroom drops)\n *\n * Wire it up:\n *\n * <PromptInput\n * budgetTokens={32000}\n * commands={[{ name: \"/rewrite\", hint: \"rewrite the selection\" }]}\n * mentions={[{ id: \"ada\", name: \"Ada\", kind: \"person\" }]}\n * onSubmit={(text, attachments) => sendToAgent(text, attachments)}\n * />\n */\nexport type PromptCmd = { name: string; hint: string };\nexport type PromptMention = {\n id: string;\n name: string;\n kind: \"agent\" | \"file\" | \"person\" | string;\n};\nexport type PromptAttachment = {\n id: string;\n name: string;\n bytes: number;\n /**\n * The file the user actually attached.\n *\n * Optional because an attachment restored from a server has no File — but for\n * anything the user dropped or picked it is always present. Without it a host\n * can render a chip and nothing else: no POST, no FormData, no send-to-model.\n */\n file?: File;\n /** MIME type. Hosts branch on this to decide whether a file can be sent as-is. */\n type?: string;\n /**\n * The host's handle for the stored bytes, from `persistAttachment`. On an\n * attachment restored from a draft WITHOUT a File, this is how the host finds\n * the bytes to send.\n */\n ref?: string;\n};\n\n/**\n * `...rest` reaches the root surface, so a consumer or a bridge can tag the\n * composer with its own `data-*` handle. The component named every prop it\n * used and had no rest spread, which means a `data-handle` put on it was\n * DROPPED — silently, since React discards unknown props and the component\n * still renders perfectly. That is issue #22 all over again, and the contract\n * calls it out: \"Each interactive element has a stable identity. Agents never\n * guess DOM.\"\n */\nexport interface PromptInputProps\n extends Omit<\n HTMLAttributes<HTMLDivElement>,\n \"onSubmit\" | \"className\" | \"onChange\" | \"defaultValue\"\n > {\n /** Extra classes on the root surface. MERGED, never replaced. */\n className?: string;\n /**\n * The text, controlled. With paste pills on it carries a marker per pill\n * (`pasteMarker(id)`), exactly as `Composer`'s `value` does. Pills and\n * attachments stay internal; use `draft` to control those too.\n */\n value?: string;\n /** The starting text, uncontrolled. Ignored when `value` or `draft` is given. */\n defaultValue?: string;\n /**\n * Called with the new text when the PERSON edits it, and with `\"\"` after a\n * send. Not called by `setDraft` or a prop change: the host made those.\n */\n onChange?: (value: string) => void;\n /**\n * The whole draft, controlled: text, paste pills and attachments, as JSON.\n * Wins over `value`. Pair with `onDraftChange`.\n */\n draft?: PromptDraft;\n /** The starting draft, uncontrolled. Wins over `defaultValue`. */\n defaultDraft?: PromptDraft;\n /**\n * Called with the whole draft whenever the person changes any part of it —\n * typing, a paste, an attach, a removal, a send (which empties it) — and when\n * `persistAttachment` resolves a `ref`. Not called by `setDraft`.\n */\n onDraftChange?: (draft: PromptDraft) => void;\n /**\n * Keep an attached file somewhere and return a handle for it (a storage key, an\n * upload URL — or a `data:` URL via `promptFileToDataUrl`). Called once per\n * attached or pasted file; the result becomes the file's `ref` in the draft.\n * May return a Promise; the draft is updated (and `onDraftChange` fires) when it\n * resolves. Omit it and a draft carries file metadata only.\n */\n persistAttachment?: (\n file: File,\n entry: PromptDraftFile,\n ) => string | undefined | Promise<string | undefined>;\n /**\n * Get a File back for a draft entry restored without one (from `setDraft`,\n * `defaultDraft` or a controlled `draft`). May return a Promise. Return\n * `undefined` when there is none: the chip or pill still shows, and the\n * attachment reaches `onSubmit` with its `ref` and no `file`.\n */\n restoreAttachment?: (\n entry: PromptDraftFile,\n ) => File | undefined | Promise<File | undefined>;\n /** Token budget for the meter. */\n budgetTokens: number;\n /** Slash-commands. Names must start with `/`. */\n commands?: PromptCmd[];\n /** @-mentions. */\n mentions?: PromptMention[];\n /** Show the keyboard hint (\"Ctrl + Enter to send\", \"⌘ + Enter\" on a Mac). */\n showHint?: boolean;\n /**\n * Which modifier the hint and the default placeholder NAME. Omit it and the\n * component reads the platform after mount, which is the right answer for an\n * ordinary web app.\n *\n * Pass it when the host knows better than `navigator` does — an Electron\n * renderer forwarding `process.platform`, a remote session where the\n * keyboard is not the one running the browser, or a test that needs the\n * branch it is not sitting on. The pressed keys are unaffected either way:\n * submit is `⌘/Ctrl+Enter` on every platform, so this only decides which\n * true key gets named.\n */\n platform?: FancyPlatform;\n /**\n * Called on ⌘/Ctrl+Enter or send button.\n *\n * **Return nothing and the draft empties at once, as it always has.**\n *\n * Return a Promise to make the send acknowledged: the text, pills and\n * attachments stay, the send button is disabled and `aria-busy`, and a second\n * send is refused until it settles. The text stays editable meanwhile.\n *\n * - Resolves (to anything but `{ ok: false }`) → the draft empties in one\n * commit, unless anything changed it while the send was in flight (the\n * person, `setDraft`, a controlled prop), even to an identical draft; the\n * newer draft is kept whole.\n * - Rejects, or resolves `{ ok: false, error? }` → the draft is kept,\n * `onSubmitError` is called and the failure is announced to screen readers.\n *\n * With a controlled `value` or `draft` the reset reaches the host through\n * `onChange(\"\")` / `onDraftChange(empty)` like any other change, and the host\n * decides whether to apply it.\n */\n onSubmit: (\n text: string,\n attachments: PromptAttachment[],\n ) => void | Promise<SubmitResult>;\n /**\n * Called when a Promise from `onSubmit` rejects (with the reason) or resolves\n * `{ ok: false }` (with its `error`, or `undefined`). The draft is kept.\n */\n onSubmitError?: (error: unknown) => void;\n /** Disables the text, attaching, dropping and the send. */\n disabled?: boolean;\n /**\n * Disables only the send: the text stays editable and attachable, and\n * ⌘/Ctrl+Enter does not send. For \"the agent is still answering, but you can\n * write the next message\".\n */\n sendDisabled?: boolean;\n /**\n * Form field name for the text. On the textarea itself; with inline pills on\n * (`pasteThreshold` / `pasteImages`), on a hidden input carrying the expanded\n * text, since a contenteditable is not a form field.\n */\n name?: string;\n /**\n * `id` of the text field (textarea or inline editor). `id` itself names the\n * root, as it always has. It is the one handle that does not change when inline\n * pills switch the field type. Without it the field is found by\n * `data-react-fancy-prompt-input-textarea` or, with inline pills,\n * `data-react-fancy-composer-input`.\n */\n inputId?: string;\n /**\n * Placeholder text. Defaults to a hint naming the modifier THIS platform\n * actually has, so leaving it unset is the better option — a hardcoded\n * placeholder is the other place a `⌘` ends up in front of a Windows user.\n */\n placeholder?: string;\n /** Rough estimator: chars-per-token. Defaults to 4. */\n charsPerToken?: number;\n /** Color → CSS chip mapping for mention kinds. */\n mentionColor?: Record<string, string>;\n /** Optional max textarea height in px. Defaults to 280. */\n maxHeight?: number;\n /**\n * Rendered INSIDE the rounded shell, ABOVE the attachments bar and\n * textarea. Use this slot for a drawer of tools/files/prompts/etc. so\n * the drawer and composer share one visual panel. See {@link ChatDrawer}.\n */\n aboveInput?: ReactNode;\n /**\n * Hold a paste of at least this many characters as a pill IN the message,\n * where it was pasted: on a line of its own, its full text put back in place\n * when clicked, and expanded where it sits when sent. Omit it and paste is\n * unchanged. Setting this (or `pasteImages`) swaps the textarea for an inline\n * editor that keeps the `/` and `@` pickers and the send key.\n */\n pasteThreshold?: number;\n /**\n * A pasted image becomes an inline `Pasted image N` pill AND reaches\n * `onSubmit`'s attachments, with its File, in message order; the text marks\n * where it sat as `[Pasted image N]`. Without it a pasted image is an\n * ordinary attachment chip, as before.\n */\n pasteImages?: boolean;\n /** Characters of a held paste shown at EACH end of its hover preview. Default 20. */\n previewChars?: number;\n /** Replace the paste pills' hover popover content. */\n renderPastePreview?: (preview: ComposerPastePreview) => ReactNode;\n /** Restyle the paste pills, their popover and the inline editor. Merged onto the defaults. */\n pasteClassNames?: ComposerPasteClassNames;\n /**\n * Submit pasted images with an enclosing `<form>`, under this field name: a\n * hidden `<input type=\"file\" multiple>` holds the images still in the message,\n * in message order. Needs `pasteImages`. They reach `onSubmit` either way.\n */\n pasteFilesName?: string;\n /** `\"icon\"`: an icon-only attach button whose tooltip says what it does. Default `\"label\"`. */\n attachButton?: \"label\" | \"icon\";\n /**\n * `\"icon\"`: a compact icon send button whose tooltip carries the shortcut —\n * replacing the inline `Ctrl + Enter to send` hint. Default `\"label\"`.\n */\n sendButton?: \"label\" | \"icon\";\n /**\n * Tokens already used — the caller's REAL value, e.g. an agent's context\n * usage. The meter shows this plus the estimate for the draft. Without it the\n * meter estimates the draft alone, as before.\n */\n usedTokens?: number;\n /**\n * `\"always\"` (default) — as before. `\"near-limit\"` — hidden until usage passes\n * `meterShowAt`, muted until `meterWarnAt`, warning colour past that.\n * `\"hidden\"` — never shown.\n */\n meter?: \"always\" | \"near-limit\" | \"hidden\";\n /** Fraction of `budgetTokens` at which a `\"near-limit\"` meter appears. Default 0.75. */\n meterShowAt?: number;\n /** Fraction of `budgetTokens` past which a `\"near-limit\"` meter takes the warning colour. Default 0.9. */\n meterWarnAt?: number;\n}\n\nconst DEFAULT_MENTION_COLOR: Record<string, string> = {\n agent: \"#a855f7\",\n file: \"#10b981\",\n person: \"#3b82f6\",\n};\n\nexport const PromptInput = forwardRef<PromptInputHandle, PromptInputProps>(function PromptInput({\n value: controlledValue,\n defaultValue,\n onChange,\n draft: controlledDraft,\n defaultDraft,\n onDraftChange,\n persistAttachment,\n restoreAttachment,\n budgetTokens,\n commands = [],\n mentions = [],\n showHint = true,\n platform,\n onSubmit,\n onSubmitError,\n disabled = false,\n sendDisabled = false,\n name,\n inputId,\n placeholder,\n charsPerToken = 4,\n mentionColor,\n maxHeight = 280,\n aboveInput,\n className,\n pasteThreshold,\n pasteImages = false,\n previewChars = 20,\n renderPastePreview,\n pasteClassNames,\n pasteFilesName,\n attachButton = \"label\",\n sendButton = \"label\",\n usedTokens,\n meter = \"always\",\n meterShowAt = 0.75,\n meterWarnAt = 0.9,\n ...rest\n}: PromptInputProps, ref) {\n /** Inline pills need an editor that can hold them; a textarea cannot. */\n const inline = pasteThreshold !== undefined || pasteImages;\n\n /*\n * ONE model for everything a draft is — text, pills, attachments — so a draft\n * read back is never a text from one moment and a list from another. Pills used\n * to live in `useInlinePaste` and attachments in their own state, which is why\n * nothing outside could read or restore them. They are now owned here and handed\n * to `useInlinePaste` controlled.\n *\n * Files cannot be JSON, so they live beside the model in `filesRef`, keyed by the\n * model's ids. `filesVersion` re-renders when a restored File arrives; that is\n * not a draft change, so nobody is told about it.\n */\n const draftControlled = controlledDraft !== undefined;\n const [innerDraft, setInnerDraft] = useState<PromptDraft>(\n () =>\n (defaultDraft && readPromptDraft(defaultDraft, inline)) || {\n ...EMPTY_DRAFT,\n text: defaultValue ?? \"\",\n },\n );\n const baseDraft = draftControlled ? controlledDraft : innerDraft;\n const model: PromptDraft =\n !draftControlled && controlledValue !== undefined && controlledValue !== baseDraft.text\n ? { ...baseDraft, text: controlledValue }\n : baseDraft;\n const modelRef = useRef(model);\n modelRef.current = model;\n const text = model.text;\n\n /*\n * The draft's REVISION: bumped by every change to what the draft says — the\n * person typing, pasting, attaching or removing; the host's `setDraft`; a\n * controlled `draft` / `value` prop that now says something different. NOT by\n * a `ref` arriving from `persistAttachment`, which is storage catching up,\n * not anybody changing the message.\n *\n * An acknowledged send clears only if the revision is still the one it sent.\n * Comparing CONTENT instead (5.70.0) threw away newer drafts that happened to\n * compare equal: an edit typed and undone, a host restoring the same draft, a\n * pill or attachment changed under the same id.\n */\n const revisionRef = useRef(0);\n const seenRef = useRef(model);\n if (seenRef.current !== model) {\n // A model this component did not commit itself: a controlled prop moved.\n if (!sameDraftContent(seenRef.current, model)) revisionRef.current += 1;\n seenRef.current = model;\n }\n\n const onChangeRef = useRef(onChange);\n onChangeRef.current = onChange;\n const onDraftChangeRef = useRef(onDraftChange);\n onDraftChangeRef.current = onDraftChange;\n const persistRef = useRef(persistAttachment);\n persistRef.current = persistAttachment;\n\n /**\n * Apply a change to the draft. `notify` is false only for `setDraft`: a host\n * restoring a draft already knows what it set, and telling it would make every\n * restore look like the person typed — a \"dirty\" flag, a typing indicator, a\n * save that overwrites what was just loaded.\n */\n const commit = useCallback(\n (update: (current: PromptDraft) => PromptDraft, notify = true, revise = true) => {\n const prev = modelRef.current;\n const next = update(prev);\n if (next === prev) return;\n if (revise) revisionRef.current += 1;\n modelRef.current = next;\n seenRef.current = next;\n setInnerDraft(next);\n if (!notify) return;\n if (next.text !== prev.text) onChangeRef.current?.(next.text);\n onDraftChangeRef.current?.(next);\n },\n [],\n );\n\n const filesRef = useRef(new Map<string, File>());\n const placeholdersRef = useRef(new Map<string, File>());\n const requestedRef = useRef(new Set<string>());\n const [filesVersion, bumpFiles] = useReducer((n: number) => n + 1, 0);\n\n /**\n * Record a ref from `persistAttachment` against whichever entry has `id`. Not\n * a new revision: a send in flight that carried this file may still clear it.\n */\n const setFileRef = useCallback(\n (id: string, fileRef: string) =>\n commit(\n (m) => {\n const withRef = (f: PromptDraftFile) => (f.id === id ? { ...f, ref: fileRef } : f);\n if (!promptDraftFiles(m).some((f) => f.id === id)) return m;\n return {\n ...m,\n attachments: m.attachments.map(withRef),\n pills: m.pills.map((p) => (p.kind === \"image\" ? { ...p, file: withRef(p.file) } : p)),\n };\n },\n true,\n false,\n ),\n [commit],\n );\n\n /**\n * Hand a newly attached file to the host's store. A synchronous handle is\n * returned so it lands in the same draft as the file; an async one updates the\n * draft when it arrives. A failing store loses the handle, never the attachment.\n */\n const persist = useCallback(\n (file: File, entry: PromptDraftFile): string | undefined => {\n const store = persistRef.current;\n if (!store) return undefined;\n let out: ReturnType<typeof store>;\n try {\n out = store(file, entry);\n } catch (error) {\n console.error(\"PromptInput: persistAttachment threw\", error);\n return undefined;\n }\n if (typeof out === \"string\") return out;\n if (out && typeof (out as Promise<unknown>).then === \"function\") {\n (out as Promise<string | undefined>).then(\n (fileRef) => {\n if (typeof fileRef === \"string\") setFileRef(entry.id, fileRef);\n },\n (error) => console.error(\"PromptInput: persistAttachment rejected\", error),\n );\n }\n return undefined;\n },\n [setFileRef],\n );\n\n const fileMeta = (id: string, file: File): PromptDraftFile => ({\n id,\n name: file.name,\n type: file.type,\n bytes: file.size,\n });\n\n /** A held image needs a File to show; a restored one without its bytes gets an empty stand-in. */\n const fileFor = useCallback((entry: PromptDraftFile): File => {\n const real = filesRef.current.get(entry.id);\n if (real) return real;\n let stand = placeholdersRef.current.get(entry.id);\n if (!stand) {\n stand = new File([], entry.name, { type: entry.type });\n placeholdersRef.current.set(entry.id, stand);\n }\n return stand;\n }, []);\n\n const heldPastes = useMemo<HeldPaste[]>(\n () =>\n model.pills.flatMap((p) =>\n p.kind === \"text\"\n ? [p.label !== undefined ? { id: p.id, text: p.text, label: p.label } : { id: p.id, text: p.text }]\n : [],\n ),\n [model.pills],\n );\n const heldImages = useMemo<HeldImage[]>(\n () =>\n model.pills.flatMap((p) =>\n p.kind === \"image\" ? [{ id: p.id, label: p.label, file: fileFor(p.file) }] : [],\n ),\n // filesVersion: a restored File arrived, so the stand-in can be swapped out.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [model.pills, fileFor, filesVersion],\n );\n\n const onHeldPastesChange = useCallback(\n (next: HeldPaste[]) =>\n commit((m) => {\n const current = m.pills.filter((p) => p.kind === \"text\");\n if (\n current.length === next.length &&\n current.every(\n (p, i) =>\n p.id === next[i]!.id &&\n p.kind === \"text\" &&\n p.text === next[i]!.text &&\n p.label === next[i]!.label,\n )\n ) {\n return m;\n }\n return {\n ...m,\n pills: [\n ...next.map(\n (p): PromptDraftPill =>\n p.label !== undefined\n ? { kind: \"text\", id: p.id, text: p.text, label: p.label }\n : { kind: \"text\", id: p.id, text: p.text },\n ),\n ...m.pills.filter((p) => p.kind === \"image\"),\n ],\n };\n }),\n [commit],\n );\n\n const onHeldImagesChange = useCallback(\n (next: HeldImage[]) =>\n commit((m) => {\n const before = new Map(\n m.pills.flatMap((p) => (p.kind === \"image\" ? [[p.id, p] as const] : [])),\n );\n if (before.size === next.length && next.every((i) => before.has(i.id))) return m;\n const images = next.map((image): PromptDraftPill => {\n const known = before.get(image.id);\n if (known && known.kind === \"image\") return known;\n // A new paste: its File is real, so keep it and offer it to the host.\n filesRef.current.set(image.id, image.file);\n const entry = fileMeta(image.id, image.file);\n const fileRef = persist(image.file, entry);\n return {\n kind: \"image\",\n id: image.id,\n label: image.label,\n file: fileRef === undefined ? entry : { ...entry, ref: fileRef },\n };\n });\n return { ...m, pills: [...m.pills.filter((p) => p.kind === \"text\"), ...images] };\n }),\n [commit, persist],\n );\n\n const editorRef = useRef<InlinePasteEditorHandle>(null);\n const paste = useInlinePaste({\n pasteThreshold,\n pasteImages,\n previewChars,\n renderPastePreview,\n classNames: pasteClassNames,\n disabled,\n heldPastes,\n onHeldPastesChange,\n heldImages,\n onHeldImagesChange,\n editorRef,\n });\n\n const attachments = useMemo<PromptAttachment[]>(\n () =>\n model.attachments.map((a) => ({\n id: a.id,\n name: a.name,\n bytes: a.bytes,\n file: filesRef.current.get(a.id),\n type: a.type,\n ...(a.ref !== undefined ? { ref: a.ref } : {}),\n })),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [model.attachments, filesVersion],\n );\n\n /*\n * Get Files back for entries that arrived without one — a restored draft, a\n * default draft, a controlled draft from storage. Each id is asked for once.\n */\n const restoreRef = useRef(restoreAttachment);\n restoreRef.current = restoreAttachment;\n useEffect(() => {\n const restore = restoreRef.current;\n if (!restore) return;\n for (const entry of promptDraftFiles(model)) {\n if (filesRef.current.has(entry.id) || requestedRef.current.has(entry.id)) continue;\n requestedRef.current.add(entry.id);\n const accept = (file: File | undefined) => {\n if (!(file instanceof Blob)) return;\n filesRef.current.set(entry.id, file as File);\n bumpFiles();\n };\n let out: ReturnType<typeof restore>;\n try {\n out = restore(entry);\n } catch (error) {\n console.error(\"PromptInput: restoreAttachment threw\", error);\n continue;\n }\n if (out && typeof (out as Promise<unknown>).then === \"function\") {\n (out as Promise<File | undefined>).then(accept, (error) =>\n console.error(\"PromptInput: restoreAttachment rejected\", error),\n );\n } else {\n accept(out as File | undefined);\n }\n }\n }, [model]);\n\n const setText = (next: string) => commit((m) => (m.text === next ? m : { ...m, text: next }));\n const [picker, setPicker] = useState<null | {\n kind: \"cmd\" | \"mention\";\n start: number;\n query: string;\n cursor: number;\n }>(null);\n const taRef = useRef<HTMLTextAreaElement>(null);\n const fileRef = useRef<HTMLInputElement>(null);\n const [dragOver, setDragOver] = useState(false);\n\n const colors = mentionColor ?? DEFAULT_MENTION_COLOR;\n\n /**\n * Resolved AFTER mount, never during render.\n *\n * `navigator` read during render disagrees with the server's HTML and\n * hydration patches it — so the fix for a wrong label would have introduced a\n * hydration mismatch instead. The first paint names `Ctrl`, which is\n * pressable on every platform including a Mac, and an Apple client swaps to\n * `⌘` on the next tick. A prop, when given, wins outright and no detection\n * runs at all.\n */\n const [detected, setDetected] = useState<FancyPlatform>(\"generic\");\n useEffect(() => {\n if (platform) return;\n setDetected(detectPlatform(browserPlatformHints()));\n }, [platform]);\n const modKey = modifierKeyLabel(platform ?? detected);\n\n const resolvedPlaceholder =\n placeholder ??\n `Ask anything. Type / for commands, @ for mentions. ${modKey}+Enter to send.`;\n\n // Estimated from what will actually be SENT — pills expanded — so a held\n // paste counts for its text, not for the few characters of its marker.\n const draftTokens = useMemo(\n () =>\n Math.ceil((inline ? paste.expand(text) : text).length / Math.max(1, charsPerToken)),\n [text, inline, paste, charsPerToken],\n );\n const tokens = (usedTokens ?? 0) + draftTokens;\n const ratio = Math.min(1, tokens / budgetTokens);\n const meterLevel: \"ok\" | \"caution\" | \"warn\" | \"muted\" | null =\n meter === \"hidden\"\n ? null\n : meter === \"near-limit\"\n ? ratio < meterShowAt\n ? null\n : ratio >= meterWarnAt\n ? \"warn\"\n : \"muted\"\n : ratio < 0.6\n ? \"ok\"\n : ratio < 0.85\n ? \"caution\"\n : \"warn\";\n const meterColor =\n meterLevel === \"ok\"\n ? \"#10b981\"\n : meterLevel === \"caution\"\n ? \"#f59e0b\"\n : meterLevel === \"warn\"\n ? \"#ef4444\"\n : \"#a1a1aa\";\n\n const filteredCmds = useMemo(\n () =>\n picker?.kind === \"cmd\"\n ? commands.filter((c) =>\n c.name.slice(1).toLowerCase().startsWith(picker.query.toLowerCase()),\n )\n : [],\n [picker, commands],\n );\n const filteredMentions = useMemo(\n () =>\n picker?.kind === \"mention\"\n ? mentions.filter((m) =>\n m.name.toLowerCase().includes(picker.query.toLowerCase()),\n )\n : [],\n [picker, mentions],\n );\n const items: Array<PromptCmd | PromptMention> =\n picker?.kind === \"cmd\"\n ? filteredCmds\n : picker?.kind === \"mention\"\n ? filteredMentions\n : [];\n\n useEffect(() => {\n const ta = taRef.current;\n if (!ta) return;\n ta.style.height = \"auto\";\n ta.style.height = Math.min(maxHeight, ta.scrollHeight) + \"px\";\n }, [text, maxHeight]);\n\n const updateText = (next: string, caret: number) => {\n setText(next);\n let triggerIdx = -1;\n let triggerKind: \"cmd\" | \"mention\" | null = null;\n for (let i = caret - 1; i >= 0; i--) {\n const ch = next[i];\n if (ch === \"@\") {\n triggerKind = \"mention\";\n triggerIdx = i;\n break;\n }\n if (ch === \"/\" && (i === 0 || /\\s/.test(next[i - 1] ?? \"\"))) {\n triggerKind = \"cmd\";\n triggerIdx = i;\n break;\n }\n if (/\\s/.test(ch)) break;\n }\n if (triggerKind !== null && triggerIdx >= 0) {\n const query = next.slice(triggerIdx + 1, caret);\n setPicker({ kind: triggerKind, start: triggerIdx, query, cursor: 0 });\n } else {\n setPicker(null);\n }\n };\n\n const insertChoice = (i: number) => {\n if (!picker || items.length === 0) return;\n const choice = items[i] ?? items[0];\n const insert =\n picker.kind === \"cmd\"\n ? (choice as PromptCmd).name + \" \"\n : `@${(choice as PromptMention).id} `;\n const before = text.slice(0, picker.start);\n const after = text.slice(picker.start + 1 + picker.query.length);\n const next = before + insert + after;\n setText(next);\n setPicker(null);\n if (inline) {\n // The editor rebuilds from the value and puts the caret back itself.\n editorRef.current?.setValue(next, before.length + insert.length);\n return;\n }\n requestAnimationFrame(() => {\n const ta = taRef.current;\n if (!ta) return;\n ta.focus();\n const pos = before.length + insert.length;\n ta.setSelectionRange(pos, pos);\n });\n };\n\n const ack = useAcknowledgedSubmit(onSubmitError);\n const submitting = ack.status === \"submitting\";\n\n const submit = () => {\n if (disabled || sendDisabled || ack.busy()) return;\n // A message that is only a paste counts: its marker is in `text`.\n if (!text.trim() && attachments.length === 0) return;\n\n let result: ReturnType<typeof onSubmit>;\n if (inline) {\n // Every pill goes out where it sits; pasted images join the attachments\n // in message order, with their Files, so a host sends them like any other.\n // A restored image without its bytes goes out with its `ref` and no `file`,\n // never with the empty stand-in that only exists so the pill can render.\n const pastedImages: PromptAttachment[] = paste.imagesIn(text).map((image) => {\n const entry = model.pills.find((p) => p.id === image.id);\n const meta = entry?.kind === \"image\" ? entry.file : fileMeta(image.id, image.file);\n return {\n id: image.id,\n name: meta.name,\n bytes: meta.bytes,\n file: filesRef.current.get(image.id),\n type: meta.type,\n ...(meta.ref !== undefined ? { ref: meta.ref } : {}),\n };\n });\n result = onSubmit(paste.expand(text), [...attachments, ...pastedImages]);\n } else {\n result = onSubmit(text, attachments);\n }\n setPicker(null);\n const sent = revisionRef.current;\n ack.track(result, () => {\n /*\n * A void `onSubmit` reaches here synchronously, so this is the reset it\n * always was. An async one reaches here when it resolves: if anything\n * changed the draft while it was in flight — the person, the host's\n * `setDraft`, a controlled prop — that draft is newer than the send and is\n * kept, even if it reads the same. Discarding it is the one outcome\n * nobody can undo.\n */\n if (revisionRef.current !== sent) return;\n // One commit for the whole reset, so a host saving on change sees one empty\n // draft rather than three half-empty ones.\n commit(() => EMPTY_DRAFT);\n filesRef.current.clear();\n placeholdersRef.current.clear();\n requestedRef.current.clear();\n });\n };\n\n /**\n * Focus requested by `setDraft(..., { focus: true })` waits for the render that\n * shows the draft: the inline editor rebuilds its DOM from the new value, which\n * would discard a caret placed before it. Children's layout effects run first,\n * so by the time this one runs the editor already holds the draft.\n */\n const pendingFocus = useRef<PromptFocusOptions | null>(null);\n /**\n * Focus on the HOST's behalf: never scrolls unless asked. A bare `focus()`\n * scrolls every scrollable ancestor up to the document to reveal the input,\n * which scrolled Genie's whole window (genie#1290). Focus that answers the\n * person's own keypress (the picker) keeps the browser's default.\n */\n const focusInput = useCallback(\n (options?: PromptFocusOptions) => {\n const opts: FocusOptions = { preventScroll: options?.preventScroll ?? true };\n if (inline) {\n editorRef.current?.focus(opts);\n return;\n }\n const ta = taRef.current;\n if (!ta) return;\n ta.focus(opts);\n ta.setSelectionRange(ta.value.length, ta.value.length);\n },\n [inline],\n );\n useLayoutEffect(() => {\n const pending = pendingFocus.current;\n if (!pending) return;\n pendingFocus.current = null;\n focusInput(pending);\n });\n\n useImperativeHandle(\n ref,\n () => ({\n getDraft: () => clonePromptDraft(modelRef.current),\n setDraft(next: PromptDraft, options?: PromptSetDraftOptions) {\n const read = readPromptDraft(next, inline);\n if (!read) {\n console.warn(\n \"PromptInput: setDraft ignored a draft it does not recognise (expected { v: 1, ... }).\",\n next,\n );\n return;\n }\n // Ask again for any File this draft needs that is not already held.\n for (const entry of promptDraftFiles(read)) {\n if (!filesRef.current.has(entry.id)) requestedRef.current.delete(entry.id);\n }\n setPicker(null);\n commit(() => read, false);\n // `commit` always sets state here (`read` is a new object), so a render\n // follows and the layout effect below picks the focus up.\n if (options?.focus) pendingFocus.current = { preventScroll: options.preventScroll };\n },\n focus: (options?: PromptFocusOptions) => focusInput(options),\n }),\n [inline, commit, focusInput],\n );\n\n const onKeyDown = (e: React.KeyboardEvent<HTMLElement>) => {\n // Enter that confirms an IME composition finishes a word: it neither picks\n // from a menu nor sends.\n if (e.key === \"Enter\" && isImeComposing(e)) return;\n if (picker) {\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n setPicker((p) =>\n p ? { ...p, cursor: Math.min(items.length - 1, p.cursor + 1) } : p,\n );\n return;\n }\n if (e.key === \"ArrowUp\") {\n e.preventDefault();\n setPicker((p) => (p ? { ...p, cursor: Math.max(0, p.cursor - 1) } : p));\n return;\n }\n // Tab completes, as Enter does: the autocomplete key people expect from a\n // shell or Claude Code. Only while a menu is open; otherwise Tab moves\n // focus as usual.\n if (((e.key === \"Enter\" && !e.metaKey && !e.ctrlKey) || (e.key === \"Tab\" && !e.shiftKey)) && items.length > 0) {\n e.preventDefault();\n insertChoice(picker.cursor);\n return;\n }\n if (e.key === \"Escape\") {\n e.preventDefault();\n setPicker(null);\n return;\n }\n }\n if (e.key === \"Enter\" && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n submit();\n }\n };\n\n /**\n * Build attachments from a FileList, KEEPING the File.\n *\n * Shared by the drop handler and the picker so the two cannot drift — the\n * picker being a second, subtly different copy of this is exactly how one of\n * them would end up dropping the File again.\n */\n const attach = (list: FileList | File[] | null) => {\n const files = Array.from(list ?? []);\n if (files.length === 0 || disabled) return;\n\n const added = files.map((f) => {\n const id = crypto.randomUUID();\n filesRef.current.set(id, f);\n const entry = fileMeta(id, f);\n const fileRef = persist(f, entry);\n return fileRef === undefined ? entry : { ...entry, ref: fileRef };\n });\n commit((m) => ({ ...m, attachments: [...m.attachments, ...added] }));\n };\n\n const onDrop = (e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n setDragOver(false);\n attach(e.dataTransfer.files);\n };\n\n /**\n * Paste a screenshot, get an attachment.\n *\n * There was no paste handler at all, so a pasted image did nothing — no chip,\n * no error — and a host that had swapped its own paste-capable textarea for\n * this component lost the feature without noticing.\n *\n * Files attach only when the clipboard carries NO plain text. Word, Excel and\n * most editors put a picture of the selection beside the text; attaching that\n * would turn every pasted sentence into a screenshot of the sentence. A pure\n * image paste has nothing for the browser to insert, so the default is\n * prevented only then, and a text paste is never touched.\n */\n const onPaste = (e: React.ClipboardEvent<HTMLTextAreaElement>) => {\n const data = e.clipboardData;\n const files = Array.from(data?.files ?? []);\n\n if (files.length === 0 || (data?.getData(\"text/plain\") ?? \"\") !== \"\") return;\n\n e.preventDefault();\n attach(files);\n };\n\n return (\n <div\n // Spread FIRST so an internal handler or data attribute cannot be\n // clobbered from outside, and so a missing one fails loudly here rather\n // than at a consumer's selector. `className` is merged below instead.\n {...rest}\n // The component had no root handle at all, so an agent reading the\n // composer had to guess at classes. The drag state is on it too: a bridge\n // that wants to know whether a drop is in flight should not have to infer\n // it from Tailwind colours.\n data-react-fancy-prompt-input=\"\"\n data-react-fancy-prompt-input-drag={dragOver ? \"over\" : \"idle\"}\n // Absent while idle; \"submitting\" while an onSubmit Promise is pending;\n // \"error\" after one was refused, until the next send.\n data-submit-state={ack.status === \"idle\" ? undefined : ack.status}\n onDragOver={(e) => {\n e.preventDefault();\n if (!disabled) setDragOver(true);\n }}\n onDragLeave={() => setDragOver(false)}\n onDrop={onDrop}\n className={cn(\n \"relative rounded-md border bg-[color:var(--fancy-surface,var(--color-white))] transition dark:bg-[color:var(--fancy-surface-dark,var(--color-zinc-900))]\",\n dragOver\n ? \"border-violet-400 bg-violet-50/50 dark:border-violet-600 dark:bg-violet-950/30\"\n : \"border-zinc-200 dark:border-zinc-800\",\n disabled && \"opacity-50\",\n className,\n )}\n >\n {aboveInput && (\n <div className=\"border-b border-zinc-200 dark:border-zinc-800\">\n {aboveInput}\n </div>\n )}\n {attachments.length > 0 && (\n <div className=\"flex flex-wrap gap-1.5 border-b border-zinc-200 px-3 py-2 dark:border-zinc-800\">\n {attachments.map((a) => (\n <span\n key={a.id}\n // Stable handle per chip, so a host or agent can address one\n // attachment without guessing at markup.\n data-react-fancy-prompt-input-attachment={a.id}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-[color:var(--fancy-surface-muted,var(--color-zinc-100))] px-2 py-0.5 text-[11px] dark:bg-[color:var(--fancy-surface-muted-dark,var(--color-zinc-800))]\"\n >\n <Icon name=\"paperclip\" size=\"xs\" className=\"text-zinc-400\" />\n <span className=\"font-mono\">{a.name}</span>\n <span className=\"text-zinc-400\">{fmtSize(a.bytes)}</span>\n <button\n onClick={() =>\n commit((m) => ({\n ...m,\n attachments: m.attachments.filter((x) => x.id !== a.id),\n }))\n }\n disabled={disabled || undefined}\n className=\"opacity-50 hover:opacity-100\"\n aria-label=\"Remove attachment\"\n >\n ×\n </button>\n </span>\n ))}\n </div>\n )}\n\n <div className=\"relative\">\n {inline ? (\n <>\n <InlinePasteEditor\n ref={editorRef}\n value={text}\n known={paste.known}\n onChange={(next, caret) => {\n updateText(next, caret);\n paste.prune(next);\n }}\n onSubmit={submit}\n submitKey=\"mod-enter\"\n onKeyDown={onKeyDown}\n onPaste={paste.decidePaste}\n onHostsChange={paste.onHostsChange}\n placeholder={resolvedPlaceholder}\n disabled={disabled}\n id={inputId}\n maxHeight={maxHeight}\n placeholderClassName=\"left-3 top-2.5 text-[14px] leading-relaxed\"\n className={cn(\n \"block min-h-[5.5rem] px-3 py-2.5 text-[14px] leading-relaxed\",\n pasteClassNames?.input,\n )}\n />\n {paste.portals}\n {/* A contenteditable is no form field, so `name` rides on a hidden\n one carrying what a send would carry: the expanded text. */}\n {name && <input type=\"hidden\" name={name} value={paste.expand(text)} />}\n {pasteImages && pasteFilesName && (\n <PastedFilesInput\n name={pasteFilesName}\n files={paste\n .imagesIn(text)\n .flatMap((i) => (filesRef.current.has(i.id) ? [i.file] : []))}\n />\n )}\n </>\n ) : (\n <textarea\n ref={taRef}\n // Its own handle, NOT the inline editor's `data-react-fancy-composer-input`:\n // that one already means \"the contenteditable is on\", and a host that\n // tests for it to pick a write path must not be handed a textarea.\n data-react-fancy-prompt-input-textarea=\"\"\n id={inputId}\n name={name}\n disabled={disabled}\n value={text}\n onChange={(e) => updateText(e.target.value, e.target.selectionStart)}\n onKeyDown={onKeyDown}\n onPaste={onPaste}\n placeholder={resolvedPlaceholder}\n spellCheck={false}\n className=\"block w-full resize-none bg-transparent px-3 py-2.5 text-[14px] leading-relaxed outline-none placeholder:text-zinc-400\"\n rows={3}\n />\n )}\n\n {picker && items.length > 0 && (\n <div className=\"absolute bottom-full left-2 z-10 mb-1 w-72 overflow-hidden rounded-md border border-[color:var(--fancy-border,var(--color-zinc-200))] bg-[color:var(--fancy-surface,var(--color-white))] shadow-lg dark:border-[color:var(--fancy-border-dark,var(--color-zinc-700))] dark:bg-[color:var(--fancy-surface-dark,var(--color-zinc-900))]\">\n <div className=\"border-b border-zinc-100 bg-zinc-50 px-2 py-1 text-[10px] font-semibold uppercase tracking-wider text-zinc-500 dark:border-zinc-800 dark:bg-zinc-950\">\n {picker.kind === \"cmd\" ? \"Commands\" : \"Mention\"} · {items.length}\n </div>\n <ul className=\"max-h-56 overflow-y-auto\">\n {items.map((item, i) => {\n const active = i === picker.cursor;\n if (picker.kind === \"cmd\") {\n const c = item as PromptCmd;\n return (\n <li\n key={c.name}\n onMouseDown={(e) => {\n e.preventDefault();\n insertChoice(i);\n }}\n onMouseEnter={() =>\n setPicker((p) => (p ? { ...p, cursor: i } : p))\n }\n className={`cursor-pointer px-2 py-1.5 text-[12px] ${\n active\n ? \"bg-violet-100 dark:bg-violet-900/30\"\n : \"hover:bg-zinc-50 dark:hover:bg-zinc-800\"\n }`}\n >\n <div className=\"font-mono font-medium text-violet-700 dark:text-violet-300\">\n {c.name}\n </div>\n <div className=\"text-[11px] text-zinc-500\">{c.hint}</div>\n </li>\n );\n }\n const m = item as PromptMention;\n return (\n <li\n key={m.id}\n onMouseDown={(e) => {\n e.preventDefault();\n insertChoice(i);\n }}\n onMouseEnter={() =>\n setPicker((p) => (p ? { ...p, cursor: i } : p))\n }\n className={`flex cursor-pointer items-center gap-2 px-2 py-1.5 text-[12px] ${\n active\n ? \"bg-violet-100 dark:bg-violet-900/30\"\n : \"hover:bg-zinc-50 dark:hover:bg-zinc-800\"\n }`}\n >\n <span\n className=\"h-2 w-2 rounded-full\"\n style={{ backgroundColor: colors[m.kind] ?? \"#71717a\" }}\n />\n <span className=\"font-medium\">{m.name}</span>\n <span className=\"ml-auto text-[10px] uppercase tracking-wider text-zinc-400\">\n {m.kind}\n </span>\n </li>\n );\n })}\n </ul>\n </div>\n )}\n </div>\n\n <div className=\"flex items-center gap-2 border-t border-zinc-200 bg-zinc-50/60 px-3 py-2 dark:border-zinc-800 dark:bg-zinc-900/40\">\n <Tooltip content={attachButton === \"icon\" ? \"Attach files — or drop them here\" : \"Drop files here, or click\"}>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n icon=\"paperclip\"\n className=\"shrink-0\"\n data-react-fancy-prompt-input-attach=\"\"\n // An icon alone has no name; the tooltip is not one a screen reader reads.\n aria-label={attachButton === \"icon\" ? \"Attach files\" : undefined}\n disabled={disabled}\n onClick={() => fileRef.current?.click()}\n >\n {attachButton === \"icon\" ? null : \"attach\"}\n </Button>\n </Tooltip>\n {/*\n The button above shipped with a no-op onClick and a tooltip reading\n \"Drop files here, or click\" — an affordance that lied. Drop-only also\n meant keyboard and touch users could not attach at all.\n\n It then shipped with a literal paperclip EMOJI as its icon, which\n needs a COLOUR\n EMOJI FONT — not merely broad coverage — so a lean container renders a\n tofu box where the affordance should be. `icon=\"paperclip\"` draws an\n SVG from the kit's own icon layer, which is the same reason the rest of\n the suite does not hand-roll iconography.\n */}\n <input\n ref={fileRef}\n type=\"file\"\n multiple\n disabled={disabled}\n className=\"hidden\"\n onChange={(e) => {\n attach(e.target.files);\n // Let the same file be picked twice in a row.\n e.target.value = \"\";\n }}\n />\n {meterLevel !== null && (\n <div\n data-react-fancy-prompt-input-meter=\"\"\n data-level={meterLevel}\n className=\"ml-2 flex min-w-0 items-center gap-1.5 overflow-hidden\"\n >\n <div className=\"h-1.5 w-24 shrink overflow-hidden rounded-full bg-[color:var(--fancy-border,var(--color-zinc-200))] dark:bg-[color:var(--fancy-border-dark,var(--color-zinc-700))]\">\n <div\n className=\"h-full rounded-full transition-all\"\n style={{ width: `${ratio * 100}%`, backgroundColor: meterColor }}\n />\n </div>\n <span className=\"font-mono text-[11px]\" style={{ color: meterColor }}>\n {fmtTokens(tokens)} / {fmtTokens(budgetTokens)}\n </span>\n </div>\n )}\n <div className=\"ml-auto flex shrink-0 items-center gap-2\">\n {/* With an icon send button the shortcut lives in its tooltip instead. */}\n {showHint && sendButton !== \"icon\" && (\n <span\n className=\"hidden text-[10px] text-zinc-500 sm:inline\"\n data-react-fancy-prompt-input-hint={modKey === \"⌘\" ? \"apple\" : \"generic\"}\n >\n <kbd className=\"rounded border border-[color:var(--fancy-border-strong,var(--color-zinc-300))] bg-zinc-50 px-1 py-0.5 font-mono text-[9px] text-[color:var(--fancy-text-soft,var(--color-zinc-700))] dark:border-[color:var(--fancy-border-strong-dark,var(--color-zinc-700))] dark:bg-zinc-800 dark:text-[color:var(--fancy-text-soft-dark,var(--color-zinc-300))]\">\n {modKey}\n </kbd>{\" \"}\n +{\" \"}\n <kbd className=\"rounded border border-[color:var(--fancy-border-strong,var(--color-zinc-300))] bg-zinc-50 px-1 py-0.5 font-mono text-[9px] text-[color:var(--fancy-text-soft,var(--color-zinc-700))] dark:border-[color:var(--fancy-border-strong-dark,var(--color-zinc-700))] dark:bg-zinc-800 dark:text-[color:var(--fancy-text-soft-dark,var(--color-zinc-300))]\">\n Enter\n </kbd>{\" \"}\n to send\n </span>\n )}\n {sendButton === \"icon\" ? (\n <Tooltip content={`Send · ${modKey}+Enter`}>\n <Button\n color=\"violet\"\n size=\"sm\"\n icon=\"arrow-up\"\n className=\"shrink-0\"\n data-react-fancy-prompt-input-send=\"\"\n aria-label=\"Send\"\n disabled={disabled || sendDisabled}\n loading={submitting}\n aria-busy={submitting || undefined}\n onClick={submit}\n />\n </Tooltip>\n ) : (\n <Button\n color=\"violet\"\n size=\"sm\"\n iconTrailing=\"arrow-right\"\n className=\"shrink-0\"\n data-react-fancy-prompt-input-send=\"\"\n disabled={disabled || sendDisabled}\n loading={submitting}\n aria-busy={submitting || undefined}\n onClick={submit}\n >\n send\n </Button>\n )}\n {ack.status === \"error\" && (\n // Mounted with its text, as `role=\"alert\"` is announced on insertion.\n // The root is positioned, so the clipped box stays inside it.\n <div\n role=\"alert\"\n data-react-fancy-prompt-input-error=\"\"\n className=\"sr-only\"\n style={visuallyHidden}\n >\n {ack.message}\n </div>\n )}\n </div>\n </div>\n </div>\n );\n});\n\n/**\n * Whether two drafts SAY the same thing, for a controlled prop that arrives as\n * a new object: the host echoing what it was told (a spread, a clone) is not a\n * new revision. Every field counts except a file's `ref`, which is where the\n * bytes are stored rather than what the message is. Cheap in the common case:\n * an echo shares its arrays, so the full comparison only runs when they differ.\n */\nfunction sameDraftContent(a: PromptDraft, b: PromptDraft): boolean {\n if (a.text !== b.text) return false;\n if (a.pills === b.pills && a.attachments === b.attachments) return true;\n const key = (d: PromptDraft) =>\n JSON.stringify([\n d.pills.map((p) =>\n p.kind === \"text\" ? [p.kind, p.id, p.text, p.label] : [p.kind, p.id, p.label, fileKey(p.file)],\n ),\n d.attachments.map(fileKey),\n ]);\n return key(a) === key(b);\n}\n\nfunction fileKey(f: PromptDraftFile): unknown[] {\n return [f.id, f.name, f.type, f.bytes];\n}\n\nfunction fmtTokens(n: number): string {\n if (n < 1000) return `${n}`;\n return `${(n / 1000).toFixed(1)}k`;\n}\n\nfunction fmtSize(b: number): string {\n if (b < 1024) return `${b} B`;\n if (b < 1024 * 1024) return `${(b / 1024).toFixed(0)} KB`;\n return `${(b / 1024 / 1024).toFixed(1)} MB`;\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useAcknowledgedSubmit } from './chunk-
|
|
1
|
+
import { useAcknowledgedSubmit } from './chunk-J6IQHJET.js';
|
|
2
2
|
import { useInlinePaste, withSeparators, endsOf, InlinePasteEditor, PastedFilesInput } from './chunk-6PRXG7L6.js';
|
|
3
3
|
import { visuallyHidden } from './chunk-I3LAFKJU.js';
|
|
4
4
|
import { isImeComposing } from './chunk-S42OGG43.js';
|
|
@@ -85,16 +85,23 @@ var Composer = forwardRef(
|
|
|
85
85
|
}, [inline]);
|
|
86
86
|
const ack = useAcknowledgedSubmit(onSubmitError);
|
|
87
87
|
const submitting = ack.status === "submitting";
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
|
|
88
|
+
const heldImages = paste.heldImages;
|
|
89
|
+
const revisionRef = useRef(0);
|
|
90
|
+
const seenRef = useRef({ value, heldPastes, heldImages });
|
|
91
|
+
const seen = seenRef.current;
|
|
92
|
+
if (seen.value !== value || seen.heldPastes !== heldPastes || seen.heldImages !== heldImages) {
|
|
93
|
+
if (seen.value !== value || !samePastes(seen.heldPastes, heldPastes) || !sameImages(seen.heldImages, heldImages)) {
|
|
94
|
+
revisionRef.current += 1;
|
|
95
|
+
}
|
|
96
|
+
seenRef.current = { value, heldPastes, heldImages };
|
|
97
|
+
}
|
|
91
98
|
const handleSubmit = useCallback(() => {
|
|
92
99
|
if (!hasContent || disabled || sendDisabled || ack.busy()) return;
|
|
93
100
|
const images = inline ? paste.imagesIn(value) : [];
|
|
94
101
|
const result = onSubmit?.(composeOutgoing(), { images });
|
|
95
|
-
const sent =
|
|
102
|
+
const sent = revisionRef.current;
|
|
96
103
|
ack.track(result, () => {
|
|
97
|
-
if (
|
|
104
|
+
if (revisionRef.current !== sent) return;
|
|
98
105
|
setValue("");
|
|
99
106
|
paste.clear();
|
|
100
107
|
});
|
|
@@ -264,7 +271,13 @@ ${text}` : text);
|
|
|
264
271
|
}
|
|
265
272
|
);
|
|
266
273
|
Composer.displayName = "Composer";
|
|
274
|
+
function samePastes(a, b) {
|
|
275
|
+
return a.length === b.length && a.every((p, i) => p.id === b[i].id && p.text === b[i].text && p.label === b[i].label);
|
|
276
|
+
}
|
|
277
|
+
function sameImages(a, b) {
|
|
278
|
+
return a.length === b.length && a.every((img, i) => img.id === b[i].id && img.label === b[i].label && img.file === b[i].file);
|
|
279
|
+
}
|
|
267
280
|
|
|
268
281
|
export { Composer };
|
|
269
|
-
//# sourceMappingURL=chunk-
|
|
270
|
-
//# sourceMappingURL=chunk-
|
|
282
|
+
//# sourceMappingURL=chunk-GDH43QH6.js.map
|
|
283
|
+
//# sourceMappingURL=chunk-GDH43QH6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/Composer/Composer.tsx"],"names":["Composer","paste"],"mappings":"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","file":"chunk-GDH43QH6.js","sourcesContent":["import { forwardRef, useCallback, useMemo, useRef } from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { isImeComposing } from \"../../utils/ime\";\nimport { visuallyHidden } from \"../../utils/visually-hidden\";\nimport { useAcknowledgedSubmit } from \"../../hooks/use-acknowledged-submit\";\nimport { useControllableState } from \"../../hooks/use-controllable-state\";\nimport {\n endsOf,\n InlinePasteEditor,\n PastedFilesInput,\n useInlinePaste,\n withSeparators,\n type HeldImage,\n type HeldPaste,\n type InlinePasteEditorHandle,\n} from \"../PastePills\";\nimport type { ComposerProps } from \"./Composer.types\";\n\n/** Characters of a held paste shown at each end of the hover preview, unless `previewChars` says otherwise. */\nconst PREVIEW_CHARS = 20;\n\nexport const Composer = forwardRef<HTMLDivElement, ComposerProps>(\n function Composer(\n {\n value: controlledValue,\n defaultValue = \"\",\n onChange,\n onSubmit,\n onSubmitError,\n placeholder = \"Type a message...\",\n actions,\n disabled = false,\n sendDisabled = false,\n name,\n inputId,\n className,\n pasteThreshold,\n pastePlacement = \"above\",\n pasteImages = false,\n previewChars = PREVIEW_CHARS,\n renderPastePreview,\n classNames,\n heldPastes: controlledHeldPastes,\n onHeldPastesChange,\n heldImages: controlledHeldImages,\n onHeldImagesChange,\n pasteFilesName,\n },\n ref,\n ) {\n const inline = pastePlacement === \"inline\";\n const [value, setValue] = useControllableState(\n controlledValue,\n defaultValue,\n onChange,\n );\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n const editorRef = useRef<InlinePasteEditorHandle>(null);\n /*\n * The holding logic lives in `usePastePills`, wrapped by `useInlinePaste` for\n * the inline placement — which `PromptInput` shares, so the two components\n * cannot disagree about what a pill means. The rule that must not drift is\n * the one below: a send with pills still closed carries the text.\n */\n const paste = useInlinePaste({\n pasteThreshold,\n pasteImages,\n previewChars,\n renderPastePreview,\n classNames,\n disabled,\n heldPastes: controlledHeldPastes,\n onHeldPastesChange,\n heldImages: controlledHeldImages,\n onHeldImagesChange,\n editorRef,\n });\n const pills = paste.pills;\n const heldPastes = paste.heldPastes;\n const pastedFiles = useMemo(\n () => (inline && pasteImages ? paste.imagesIn(value).map((i) => i.file) : []),\n [inline, pasteImages, paste, value],\n );\n\n const handlePaste = useCallback(\n (e: React.ClipboardEvent<HTMLTextAreaElement>) => {\n // Composable by design, so this reads as a question rather than a handover.\n // Nothing else here wants the paste, so the answer is simply ignored.\n pills.handlePaste(e);\n },\n [pills],\n );\n\n const handleInlineChange = useCallback(\n (next: string) => {\n setValue(next);\n paste.prune(next);\n },\n [setValue, paste],\n );\n\n /** Everything the person believes they are sending, in the order they built it. */\n const composeOutgoing = useCallback(() => {\n if (inline) return paste.expand(value);\n\n return [value, ...heldPastes.map((p) => p.text)].filter(Boolean).join(\"\\n\\n\");\n }, [inline, value, heldPastes, paste]);\n\n const hasContent = value.trim().length > 0 || (!inline && heldPastes.length > 0);\n\n const focusInput = useCallback(() => {\n if (inline) editorRef.current?.focus();\n else textareaRef.current?.focus();\n }, [inline]);\n\n const ack = useAcknowledgedSubmit(onSubmitError);\n const submitting = ack.status === \"submitting\";\n\n /*\n * The draft's REVISION: bumped by every render in which the text, a held\n * paste or a held image differs from the last one seen — the person typing\n * or pasting, or a host moving a controlled `value` / `heldPastes` /\n * `heldImages`. An acknowledged send clears only if the revision is still\n * the one it sent. Comparing content instead (5.70.0) threw away newer\n * drafts that happened to compare equal: an edit typed and undone, a pill\n * whose text the host changed under the same id.\n */\n const heldImages = paste.heldImages;\n const revisionRef = useRef(0);\n const seenRef = useRef({ value, heldPastes, heldImages });\n const seen = seenRef.current;\n if (seen.value !== value || seen.heldPastes !== heldPastes || seen.heldImages !== heldImages) {\n if (\n seen.value !== value ||\n !samePastes(seen.heldPastes, heldPastes) ||\n !sameImages(seen.heldImages, heldImages)\n ) {\n revisionRef.current += 1;\n }\n seenRef.current = { value, heldPastes, heldImages };\n }\n\n const handleSubmit = useCallback(() => {\n if (!hasContent || disabled || sendDisabled || ack.busy()) return;\n\n /*\n * THE DANGEROUS STATE, and the reason this feature needed a spec rather than\n * a guess. A pill reads as \"your paste is attached\" — so a send that drops it\n * means asking a question about a document without the document, and nobody\n * can tell: the sender believes it went, the receiver sees a question about\n * nothing.\n *\n * Above the input, held pastes go out in arrival order after what was typed,\n * because people type the question BEFORE pasting the thing it is about.\n * Inline, every pill goes out exactly where it sits.\n */\n const images = inline ? paste.imagesIn(value) : [];\n\n const result = onSubmit?.(composeOutgoing(), { images });\n const sent = revisionRef.current;\n ack.track(result, () => {\n /*\n * A void `onSubmit` reaches here synchronously, so `sent` is still the\n * current revision and this clears exactly as it always has. An async one\n * reaches here when it resolves: if anything changed the message while it\n * was in flight, that message is newer than the send, even if it reads the\n * same, and throwing it away is the one outcome nobody can undo. So only\n * the revision that was sent is cleared.\n */\n if (revisionRef.current !== sent) return;\n setValue(\"\");\n paste.clear();\n });\n // Focus goes back to the text at once, as before: it stays editable while\n // a send is in flight, and the send button the person may have clicked is\n // about to be disabled.\n focusInput();\n }, [hasContent, disabled, sendDisabled, ack, value, inline, paste, onSubmit, composeOutgoing, setValue, focusInput]);\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\n // Enter that confirms an IME composition finishes a word; it is not a send.\n if (e.key === \"Enter\" && !e.shiftKey && !isImeComposing(e)) {\n e.preventDefault();\n handleSubmit();\n }\n };\n\n /** Above the input: click a pill to dump it in, APPENDED to what is already typed. */\n const insertPaste = useCallback(\n (held: HeldPaste) => {\n // `expand` removes it and hands back the text; inserting is this\n // component's job because the hook owns no DOM.\n const text = pills.expand(held.id);\n\n if (text === undefined) return;\n\n setValue((current) => (current ? `${current}\\n\\n${text}` : text));\n textareaRef.current?.focus();\n },\n [pills, setValue],\n );\n\n return (\n <div\n data-react-fancy-composer=\"\"\n // Absent while idle; \"submitting\" while an onSubmit Promise is pending;\n // \"error\" after one was refused, until the next send.\n data-submit-state={ack.status === \"idle\" ? undefined : ack.status}\n ref={ref}\n className={cn(\n \"rounded-xl border border-[color:var(--fancy-border,var(--color-zinc-200))] bg-[color:var(--fancy-surface,var(--color-white))] dark:border-[color:var(--fancy-border-dark,var(--color-zinc-700))] dark:bg-[color:var(--fancy-surface-dark,var(--color-zinc-900))]\",\n disabled && \"opacity-50\",\n className,\n )}\n >\n {!inline && heldPastes.length > 0 && (\n <div\n data-react-fancy-composer-paste-pills=\"\"\n className=\"flex flex-wrap gap-1.5 px-3 pt-3\"\n >\n {heldPastes.map((paste) => (\n // `group` + `group-hover` rather than hover state: the preview is\n // presentation, and a useState for it would be one more thing an\n // agent could desynchronise.\n <span key={paste.id} className=\"group relative inline-flex\">\n <button\n type=\"button\"\n data-react-fancy-composer-paste-pill={paste.id}\n onClick={() => insertPaste(paste)}\n disabled={disabled}\n // No `title`. A native tooltip plus the hover preview below\n // renders TWO tooltips on the same hover, at different delays.\n // The label says what clicking does; the preview says what the\n // text is, and it is linked rather than merely adjacent so a\n // screen reader reaches it too.\n aria-label={`Insert this pasted text, ${withSeparators(paste.text.length)} characters`}\n aria-describedby={`${paste.id}-preview`}\n // `pr-7` leaves room for the absolutely-positioned × that sits\n // over this button's right edge. With a smaller right padding the\n // × lands on top of the label.\n className={cn(\n \"inline-flex items-center gap-1.5 rounded-full border border-[color:var(--fancy-border,var(--color-zinc-200))] bg-zinc-50 py-1 pl-2.5 pr-7 text-xs text-zinc-700 transition-colors hover:bg-zinc-100 dark:border-[color:var(--fancy-border-dark,var(--color-zinc-700))] dark:bg-zinc-800 dark:text-zinc-200 dark:hover:bg-zinc-700\",\n classNames?.pill,\n classNames?.textPill,\n )}\n >\n <span>\n Pasted text · {withSeparators(paste.text.length)} characters\n </span>\n </button>\n\n <button\n type=\"button\"\n data-react-fancy-composer-paste-pill-remove={paste.id}\n onClick={() => pills.remove(paste.id)}\n disabled={disabled}\n aria-label=\"Discard this pasted text\"\n className={cn(\n \"absolute right-1 top-1/2 -translate-y-1/2 rounded-full px-1 text-xs text-zinc-400 transition-colors hover:text-zinc-700 dark:hover:text-zinc-100\",\n classNames?.remove,\n )}\n >\n ×\n </button>\n\n <span\n id={`${paste.id}-preview`}\n data-react-fancy-composer-paste-pill-preview={paste.id}\n role=\"tooltip\"\n className={cn(\n \"pointer-events-none absolute bottom-full left-0 z-10 mb-1 hidden max-w-xs whitespace-pre-wrap break-all rounded-md bg-zinc-900 px-2 py-1 text-xs text-white group-hover:block dark:bg-zinc-100 dark:text-zinc-900\",\n classNames?.preview,\n )}\n >\n {renderPastePreview?.({ kind: \"text\", paste }) ??\n endsOf(paste.text, previewChars)}\n </span>\n </span>\n ))}\n </div>\n )}\n\n {inline ? (\n <>\n <InlinePasteEditor\n ref={editorRef}\n value={value}\n known={paste.known}\n onChange={handleInlineChange}\n onSubmit={handleSubmit}\n onPaste={paste.decidePaste}\n onHostsChange={paste.onHostsChange}\n placeholder={placeholder}\n disabled={disabled}\n id={inputId}\n className={classNames?.input}\n />\n {paste.portals}\n {/* A contenteditable is no form field, so `name` rides on a hidden\n one carrying what a send would carry: the expanded text. */}\n {name && <input type=\"hidden\" name={name} value={composeOutgoing()} />}\n {pasteImages && pasteFilesName && (\n <PastedFilesInput name={pasteFilesName} files={pastedFiles} />\n )}\n </>\n ) : (\n <textarea\n ref={textareaRef}\n // Its own handle, NOT the inline editor's `data-react-fancy-composer-input`:\n // that one already means \"the contenteditable is on\", and a host that\n // tests for it to pick a write path must not be handed a textarea.\n data-react-fancy-composer-textarea=\"\"\n id={inputId}\n name={name}\n value={value}\n onChange={(e) => setValue(e.target.value)}\n onKeyDown={handleKeyDown}\n onPaste={handlePaste}\n placeholder={placeholder}\n disabled={disabled}\n rows={3}\n className={cn(\n \"w-full resize-none bg-transparent px-4 pt-3 text-sm outline-none placeholder:text-zinc-400\",\n classNames?.input,\n )}\n />\n )}\n <div className=\"relative flex items-center justify-between border-t border-zinc-100 px-3 py-2 dark:border-zinc-800\">\n <div className=\"flex items-center gap-1\">{actions}</div>\n {ack.status === \"error\" && (\n // Mounted with its text, as `role=\"alert\"` is announced on insertion.\n <div\n role=\"alert\"\n data-react-fancy-composer-error=\"\"\n className=\"sr-only\"\n style={visuallyHidden}\n >\n {ack.message}\n </div>\n )}\n <button\n type=\"button\"\n data-react-fancy-composer-send=\"\"\n onClick={handleSubmit}\n disabled={disabled || sendDisabled || submitting || !hasContent}\n aria-busy={submitting || undefined}\n className=\"rounded-lg bg-zinc-900 px-3 py-1.5 text-sm font-medium text-white transition-colors hover:bg-zinc-800 disabled:opacity-50 dark:bg-white dark:text-zinc-900 dark:hover:bg-zinc-100\"\n >\n Send\n </button>\n </div>\n </div>\n );\n },\n);\n\nComposer.displayName = \"Composer\";\n\n/** Same pills, same content: a host echoing a copy of what it was told is not a new revision. */\nfunction samePastes(a: HeldPaste[], b: HeldPaste[]): boolean {\n return (\n a.length === b.length &&\n a.every((p, i) => p.id === b[i]!.id && p.text === b[i]!.text && p.label === b[i]!.label)\n );\n}\n\nfunction sameImages(a: HeldImage[], b: HeldImage[]): boolean {\n return (\n a.length === b.length &&\n a.every((img, i) => img.id === b[i]!.id && img.label === b[i]!.label && img.file === b[i]!.file)\n );\n}\n"]}
|
|
@@ -27,9 +27,12 @@ function useAcknowledgedSubmit(onSubmitError) {
|
|
|
27
27
|
}
|
|
28
28
|
onErrorRef.current?.(error);
|
|
29
29
|
}, []);
|
|
30
|
+
const latest = useRef(0);
|
|
30
31
|
const track = useCallback(
|
|
31
32
|
(result, accept) => {
|
|
33
|
+
const id = ++latest.current;
|
|
32
34
|
if (!isThenable(result)) {
|
|
35
|
+
inFlight.current = false;
|
|
33
36
|
accept();
|
|
34
37
|
setStatus("idle");
|
|
35
38
|
setMessage("");
|
|
@@ -40,17 +43,23 @@ function useAcknowledgedSubmit(onSubmitError) {
|
|
|
40
43
|
setMessage("");
|
|
41
44
|
result.then(
|
|
42
45
|
(value) => {
|
|
43
|
-
|
|
46
|
+
const current = id === latest.current;
|
|
47
|
+
if (current) inFlight.current = false;
|
|
44
48
|
if (isRejection(value)) {
|
|
45
|
-
fail(value.error, value.error);
|
|
49
|
+
if (current) fail(value.error, value.error);
|
|
50
|
+
else onErrorRef.current?.(value.error);
|
|
46
51
|
return;
|
|
47
52
|
}
|
|
48
|
-
if (alive.current) {
|
|
53
|
+
if (current && alive.current) {
|
|
49
54
|
setStatus("idle");
|
|
50
55
|
accept();
|
|
51
56
|
}
|
|
52
57
|
},
|
|
53
58
|
(reason) => {
|
|
59
|
+
if (id !== latest.current) {
|
|
60
|
+
onErrorRef.current?.(reason);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
54
63
|
inFlight.current = false;
|
|
55
64
|
fail(reason, void 0);
|
|
56
65
|
}
|
|
@@ -63,5 +72,5 @@ function useAcknowledgedSubmit(onSubmitError) {
|
|
|
63
72
|
}
|
|
64
73
|
|
|
65
74
|
export { useAcknowledgedSubmit };
|
|
66
|
-
//# sourceMappingURL=chunk-
|
|
67
|
-
//# sourceMappingURL=chunk-
|
|
75
|
+
//# sourceMappingURL=chunk-J6IQHJET.js.map
|
|
76
|
+
//# sourceMappingURL=chunk-J6IQHJET.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hooks/use-acknowledged-submit.ts"],"names":[],"mappings":"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","file":"chunk-J6IQHJET.js","sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\n\n/** What an `onSubmit` Promise resolves to when the host refuses the send. */\nexport type SubmitRejection = { ok: false; error?: string };\n\n/**\n * What an `onSubmit` Promise may resolve to. Anything other than a\n * `SubmitRejection` (including `undefined`) means the send was accepted.\n */\nexport type SubmitResult = void | SubmitRejection;\n\n/**\n * `\"idle\"` — nothing in flight. `\"submitting\"` — an `onSubmit` Promise is\n * pending. `\"error\"` — the last one was refused or rejected; the draft is kept.\n */\nexport type SubmitStatus = \"idle\" | \"submitting\" | \"error\";\n\nfunction isThenable(value: unknown): value is PromiseLike<unknown> {\n return (\n value !== null &&\n (typeof value === \"object\" || typeof value === \"function\") &&\n typeof (value as { then?: unknown }).then === \"function\"\n );\n}\n\nfunction isRejection(value: unknown): value is SubmitRejection {\n return (\n value !== null &&\n typeof value === \"object\" &&\n (value as { ok?: unknown }).ok === false\n );\n}\n\n/**\n * The acknowledged-send state machine `Composer` and `PromptInput` share, so the\n * two cannot disagree about what a Promise from `onSubmit` means.\n *\n * `track(result, accept)` is handed whatever `onSubmit` returned:\n *\n * - not a Promise → `accept()` runs NOW, which is exactly the old behaviour;\n * - a Promise → `\"submitting\"` until it settles; `accept()` runs on a resolve\n * that is not `{ ok: false }`; a rejection or `{ ok: false }` leaves the draft\n * alone, moves to `\"error\"`, calls `onSubmitError` and sets `message` for the\n * live region.\n *\n * `busy()` reads a ref, not state, so two sends in the same tick (a double\n * Enter, Enter then a click) are refused before React has re-rendered.\n */\nexport function useAcknowledgedSubmit(onSubmitError?: (error: unknown) => void) {\n const [status, setStatus] = useState<SubmitStatus>(\"idle\");\n const [message, setMessage] = useState(\"\");\n const inFlight = useRef(false);\n const alive = useRef(true);\n const onErrorRef = useRef(onSubmitError);\n onErrorRef.current = onSubmitError;\n\n useEffect(() => {\n alive.current = true;\n return () => {\n alive.current = false;\n };\n }, []);\n\n const fail = useCallback((error: unknown, detail: string | undefined) => {\n if (alive.current) {\n setStatus(\"error\");\n setMessage(detail ? `Message not sent: ${detail}` : \"Message not sent. Your message has been kept.\");\n }\n onErrorRef.current?.(error);\n }, []);\n\n /*\n * Each send has an identity. Only the NEWEST may acknowledge: an older send\n * settling after a newer one started neither runs its `accept` nor ends the\n * newer send's \"submitting\". The components refuse a second send while one is\n * pending, so this is the backstop for anything that ever bypasses that guard.\n * An older send that FAILS still reaches `onSubmitError`: the host is owed\n * every failure, even one the UI has moved past.\n */\n const latest = useRef(0);\n\n const track = useCallback(\n (result: unknown, accept: () => void) => {\n const id = ++latest.current;\n if (!isThenable(result)) {\n inFlight.current = false;\n accept();\n setStatus(\"idle\");\n setMessage(\"\");\n return;\n }\n\n inFlight.current = true;\n setStatus(\"submitting\");\n setMessage(\"\");\n result.then(\n (value) => {\n const current = id === latest.current;\n if (current) inFlight.current = false;\n if (isRejection(value)) {\n if (current) fail(value.error, value.error);\n else onErrorRef.current?.(value.error);\n return;\n }\n if (current && alive.current) {\n setStatus(\"idle\");\n accept();\n }\n },\n (reason: unknown) => {\n if (id !== latest.current) {\n onErrorRef.current?.(reason);\n return;\n }\n inFlight.current = false;\n fail(reason, undefined);\n },\n );\n },\n [fail],\n );\n\n const busy = useCallback(() => inFlight.current, []);\n\n return { status, message, track, busy };\n}\n"]}
|
|
@@ -29,9 +29,12 @@ function useAcknowledgedSubmit(onSubmitError) {
|
|
|
29
29
|
}
|
|
30
30
|
onErrorRef.current?.(error);
|
|
31
31
|
}, []);
|
|
32
|
+
const latest = react.useRef(0);
|
|
32
33
|
const track = react.useCallback(
|
|
33
34
|
(result, accept) => {
|
|
35
|
+
const id = ++latest.current;
|
|
34
36
|
if (!isThenable(result)) {
|
|
37
|
+
inFlight.current = false;
|
|
35
38
|
accept();
|
|
36
39
|
setStatus("idle");
|
|
37
40
|
setMessage("");
|
|
@@ -42,17 +45,23 @@ function useAcknowledgedSubmit(onSubmitError) {
|
|
|
42
45
|
setMessage("");
|
|
43
46
|
result.then(
|
|
44
47
|
(value) => {
|
|
45
|
-
|
|
48
|
+
const current = id === latest.current;
|
|
49
|
+
if (current) inFlight.current = false;
|
|
46
50
|
if (isRejection(value)) {
|
|
47
|
-
fail(value.error, value.error);
|
|
51
|
+
if (current) fail(value.error, value.error);
|
|
52
|
+
else onErrorRef.current?.(value.error);
|
|
48
53
|
return;
|
|
49
54
|
}
|
|
50
|
-
if (alive.current) {
|
|
55
|
+
if (current && alive.current) {
|
|
51
56
|
setStatus("idle");
|
|
52
57
|
accept();
|
|
53
58
|
}
|
|
54
59
|
},
|
|
55
60
|
(reason) => {
|
|
61
|
+
if (id !== latest.current) {
|
|
62
|
+
onErrorRef.current?.(reason);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
56
65
|
inFlight.current = false;
|
|
57
66
|
fail(reason, void 0);
|
|
58
67
|
}
|
|
@@ -65,5 +74,5 @@ function useAcknowledgedSubmit(onSubmitError) {
|
|
|
65
74
|
}
|
|
66
75
|
|
|
67
76
|
exports.useAcknowledgedSubmit = useAcknowledgedSubmit;
|
|
68
|
-
//# sourceMappingURL=chunk-
|
|
69
|
-
//# sourceMappingURL=chunk-
|
|
77
|
+
//# sourceMappingURL=chunk-JKD6CFIP.cjs.map
|
|
78
|
+
//# sourceMappingURL=chunk-JKD6CFIP.cjs.map
|