@particle-academy/react-fancy 5.34.0 → 5.35.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 +111 -0
- package/dist/activity-light.cjs +2 -2
- package/dist/activity-light.js +1 -1
- package/dist/{chunk-PZTA6NCD.js → chunk-DCVR6NPP.js} +3 -3
- package/dist/chunk-DCVR6NPP.js.map +1 -0
- package/dist/{chunk-J2KYNK63.cjs → chunk-DNV7MUEE.cjs} +79 -18
- package/dist/chunk-DNV7MUEE.cjs.map +1 -0
- package/dist/{chunk-KM4R4XWB.js → chunk-O3WEV562.js} +78 -20
- package/dist/chunk-O3WEV562.js.map +1 -0
- package/dist/{chunk-EHNMKQ4O.cjs → chunk-WNUU7T62.cjs} +3 -3
- package/dist/chunk-WNUU7T62.cjs.map +1 -0
- package/dist/index-DXUAAZzE.d.cts +157 -0
- package/dist/index-DXUAAZzE.d.ts +157 -0
- package/dist/index.cjs +16 -4
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/prompt-input.cjs +3 -2
- package/dist/prompt-input.d.cts +2 -77
- package/dist/prompt-input.d.ts +2 -77
- package/dist/prompt-input.js +2 -1
- package/package.json +1 -1
- package/dist/chunk-EHNMKQ4O.cjs.map +0 -1
- package/dist/chunk-J2KYNK63.cjs.map +0 -1
- package/dist/chunk-KM4R4XWB.js.map +0 -1
- package/dist/chunk-PZTA6NCD.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,117 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
+
## [5.35.1] - 2026-10-10
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- **`ActivityLight direction="both"` drew as a tofu box in a container carrying
|
|
19
|
+
only the Liberation fonts.** The mark was U+21C4 (`⇄`), which those fonts do not
|
|
20
|
+
cover; it is now U+2194 (`↔`), which they do. **Nothing to do** — the prop, the
|
|
21
|
+
data attributes and the accessible name are unchanged, and the mark is
|
|
22
|
+
decorative (`aria-hidden`).
|
|
23
|
+
|
|
24
|
+
**One of three states failing is the expensive shape**, which is why this was
|
|
25
|
+
worth a release rather than a note. `in` and `out` drew arrows and `both` drew
|
|
26
|
+
a box, so a reader saw two working indicators and one they had no reason to
|
|
27
|
+
distrust — a font gap and a state are indistinguishable at that point, and a
|
|
28
|
+
direction mark going missing is the kind of absence nobody notices.
|
|
29
|
+
|
|
30
|
+
The choice is measured, not reasoned. `claude · tynn` rasterised each candidate
|
|
31
|
+
in a Liberation-only container and compared it pixel-for-pixel against a
|
|
32
|
+
Private Use Area codepoint no font covers: U+2190–2195 draw, U+21C4 and U+21C6
|
|
33
|
+
do not. **U+21C6 was included as a falsifier** — had it drawn, the finding
|
|
34
|
+
would have been "Liberation covers arrows" rather than "Liberation covers
|
|
35
|
+
WGL4", and this set would be the wrong fix.
|
|
36
|
+
|
|
37
|
+
Two glyph families in the kit are still outside that repertoire and are
|
|
38
|
+
deliberately NOT changed: the Dingbats (`✓` `✕` in `Checkbox`, `✦` in
|
|
39
|
+
`MagicWand`) have no in-repertoire substitute, so they are a genuine
|
|
40
|
+
glyph-versus-SVG trade rather than a one-character fix, and paying it for an
|
|
41
|
+
environment nobody ships a desktop app into is the wrong call today. A test
|
|
42
|
+
pins the `ActivityLight` set so a prettier arrow cannot drift back in.
|
|
43
|
+
|
|
44
|
+
## [5.35.0] - 2026-10-10
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
|
|
48
|
+
- **`PromptInput` told every Windows and Linux user to press a key they do not
|
|
49
|
+
have.** The keyboard hint was a hardcoded `⌘ + Enter to send`, and so was the
|
|
50
|
+
default placeholder. **Nothing to do** — the hint now names the modifier the
|
|
51
|
+
platform actually has, and the keys it accepts are unchanged (`⌘/Ctrl+Enter`
|
|
52
|
+
submits everywhere, as it always did).
|
|
53
|
+
|
|
54
|
+
It is worth more than one wrong glyph. A reader who cannot act on a hint
|
|
55
|
+
learns to ignore hints, which costs the credibility of every hint beside it.
|
|
56
|
+
In the lean Linux container where it was found, the glyph had no font behind
|
|
57
|
+
it either and rendered as a tofu box.
|
|
58
|
+
|
|
59
|
+
- New `platform?: "apple" | "generic"` prop. Omit it and the component reads
|
|
60
|
+
the platform after mount; pass it when the host knows better than
|
|
61
|
+
`navigator` — an Electron renderer forwarding `process.platform`, a remote
|
|
62
|
+
session where the keyboard is not the one running the browser, or a test
|
|
63
|
+
that needs the branch it is not sitting on.
|
|
64
|
+
- The platform is resolved AFTER mount, never during render, so this does not
|
|
65
|
+
introduce a hydration mismatch under SSR.
|
|
66
|
+
- `generic` is the fallback, and not arbitrarily: submit accepts
|
|
67
|
+
`metaKey || ctrlKey`, so `Ctrl` is a TRUE label on an Apple keyboard while
|
|
68
|
+
`⌘` is a false one everywhere else. The honest default is the pressable one.
|
|
69
|
+
- `detectPlatform(hints)` and `modifierKeyLabel(platform)` are exported for
|
|
70
|
+
any surface with a keyboard hint of its own. **Both take the platform as an
|
|
71
|
+
ARGUMENT** rather than reading a global, which is the only way a test can
|
|
72
|
+
cover the platform the developer is not sitting at.
|
|
73
|
+
|
|
74
|
+
- **`PromptInput`'s attach control rendered as a tofu box in a lean container.**
|
|
75
|
+
Its icon was a literal paperclip emoji, and an emoji needs a dedicated COLOUR
|
|
76
|
+
EMOJI FONT — not merely a font with broad coverage — which a container image
|
|
77
|
+
has no reason to carry. **Nothing to do**: it now draws `icon="paperclip"`
|
|
78
|
+
from the kit's own icon layer, as the rest of the suite does. The attachment
|
|
79
|
+
chips had the same emoji and are fixed with it.
|
|
80
|
+
|
|
81
|
+
The `send` button's trailing `->` arrow became `iconTrailing="arrow-right"` in
|
|
82
|
+
the same pass — same class of risk, same primitive already in the kit.
|
|
83
|
+
|
|
84
|
+
- **`PromptInput` dropped any `data-*` or `aria-*` handle put on it.** It named
|
|
85
|
+
every prop it used and had no rest spread, so a consumer's handle vanished
|
|
86
|
+
silently — the component contract's "agents never guess DOM" broken in the
|
|
87
|
+
one component an agent is most likely to be asked to drive. This is issue #22
|
|
88
|
+
recurring in a component that was not in that sweep. **Nothing to do**;
|
|
89
|
+
`className` now merges rather than replacing the surface's own classes, and
|
|
90
|
+
the root carries `data-react-fancy-prompt-input` whether or not a consumer
|
|
91
|
+
adds a handle.
|
|
92
|
+
|
|
93
|
+
### Added
|
|
94
|
+
|
|
95
|
+
- `data-react-fancy-prompt-input`, `-drag` (`over` / `idle`) and `-hint`
|
|
96
|
+
(`apple` / `generic`) on `PromptInput`, so a bridge reads the composer's drop
|
|
97
|
+
and platform state off the DOM instead of inferring it from Tailwind colours.
|
|
98
|
+
|
|
99
|
+
### Notes
|
|
100
|
+
|
|
101
|
+
- **A suite cannot see a font, which is why this needed a consumer to find it.**
|
|
102
|
+
Both glyph bugs passed every test here and would have passed forever: jsdom
|
|
103
|
+
has no fonts, so an emoji asserted in a DOM string is indistinguishable from
|
|
104
|
+
one a user can see. It took a screenshot of a real Electron window on Linux.
|
|
105
|
+
The new guard therefore does not test rendering — it tests the SOURCE for the
|
|
106
|
+
property that predicts the failure, that no component contains a character
|
|
107
|
+
above the Basic Multilingual Plane (the emoji dataset excepted). That line is
|
|
108
|
+
where "needs a font with good coverage" becomes "needs a second font dedicated
|
|
109
|
+
to emoji". BMP symbols are deliberately not covered: smaller risk, one
|
|
110
|
+
judgement call each, and a rule with false positives gets deleted.
|
|
111
|
+
- Measured while here, because the same report asked: **no Fancy package
|
|
112
|
+
declares a `font-family` at all**, so none of our defaults can be hijacked by
|
|
113
|
+
a generic family resolving to whatever a machine's fontconfig puts first. The
|
|
114
|
+
showcase and the starter kit both lead their stack with a named face.
|
|
115
|
+
- **This release shipped without a `lab:run`** — same dead Anthropic key as
|
|
116
|
+
5.34.0 (401 since before the previous release wave, `fancy-labs` #5). It went
|
|
117
|
+
out on a green package suite (498 tests) plus sabotage verification instead.
|
|
118
|
+
- Each fix was verified by breaking it on purpose: hardcoding the glyph back
|
|
119
|
+
fails 2 tests, defaulting an unknown platform to Apple fails 5, letting free
|
|
120
|
+
text overturn an enumerated platform fails 1, putting an emoji into a
|
|
121
|
+
different component fails the source guard, restoring the emoji icon fails 5,
|
|
122
|
+
dropping the rest spread fails 1, and letting `className` replace instead of
|
|
123
|
+
merge fails 1.
|
|
124
|
+
|
|
14
125
|
## [5.34.0] - 2026-10-09
|
|
15
126
|
|
|
16
127
|
### Added
|
package/dist/activity-light.cjs
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkWNUU7T62_cjs = require('./chunk-WNUU7T62.cjs');
|
|
4
4
|
require('./chunk-ZU5Q77OT.cjs');
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
Object.defineProperty(exports, "ActivityLight", {
|
|
9
9
|
enumerable: true,
|
|
10
|
-
get: function () { return
|
|
10
|
+
get: function () { return chunkWNUU7T62_cjs.ActivityLight; }
|
|
11
11
|
});
|
|
12
12
|
//# sourceMappingURL=activity-light.cjs.map
|
|
13
13
|
//# sourceMappingURL=activity-light.cjs.map
|
package/dist/activity-light.js
CHANGED
|
@@ -61,9 +61,9 @@ var LEVEL_CLASS = {
|
|
|
61
61
|
var DIRECTION_GLYPH = {
|
|
62
62
|
in: "\u2190",
|
|
63
63
|
out: "\u2192",
|
|
64
|
-
both: "\
|
|
64
|
+
both: "\u2194"
|
|
65
65
|
};
|
|
66
66
|
|
|
67
67
|
export { ActivityLight };
|
|
68
|
-
//# sourceMappingURL=chunk-
|
|
69
|
-
//# sourceMappingURL=chunk-
|
|
68
|
+
//# sourceMappingURL=chunk-DCVR6NPP.js.map
|
|
69
|
+
//# sourceMappingURL=chunk-DCVR6NPP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/ActivityLight/ActivityLight.tsx"],"names":[],"mappings":";;;;AAkCO,IAAM,aAAA,GAAgB,UAAA;AAAA,EAC3B,CAAC,EAAE,KAAA,EAAO,SAAA,EAAW,KAAA,GAAQ,IAAA,EAAM,KAAA,EAAO,SAAA,EAAW,GAAG,KAAA,EAAM,EAAG,GAAA,KAAQ;AAEvE,IAAA,IAAI,KAAA,KAAU,MAAM,OAAO,IAAA;AAE3B,IAAA,MAAM,UAAU,OAAO,KAAA,KAAU,QAAA,IAAY,MAAA,CAAO,SAAS,KAAK,CAAA;AAElE,IAAA,uBACE,IAAA;AAAA,MAAC,MAAA;AAAA,MAAA;AAAA,QACC,GAAA;AAAA,QACA,iCAAA,EAAiC,KAAA;AAAA,QAChC,GAAI,SAAA,GAAY,EAAE,2CAAA,EAA6C,SAAA,KAAc,EAAC;AAAA,QAC/E,YAAA,EAAY,OAAA,GAAU,CAAA,EAAG,KAAK,CAAA,EAAA,EAAK,KAAK,CAAA,EAAA,EAAK,KAAK,CAAA,CAAA,GAAK,CAAA,EAAG,KAAK,CAAA,EAAA,EAAK,KAAK,CAAA,CAAA;AAAA,QACzE,IAAA,EAAK,KAAA;AAAA,QACL,SAAA,EAAW,EAAA;AAAA,UACT,6CAAA;AAAA,UACA,YAAY,KAAK,CAAA;AAAA,UACjB;AAAA,SACF;AAAA,QACC,GAAG,KAAA;AAAA,QAEJ,QAAA,EAAA;AAAA,0BAAA,GAAA;AAAA,YAAC,MAAA;AAAA,YAAA;AAAA,cACC,qCAAA,EAAoC,EAAA;AAAA,cACpC,aAAA,EAAY,MAAA;AAAA,cACZ,SAAA,EAAW,EAAA,CAAG,yCAAA,EAA2C,KAAA,KAAU,UAAU,eAAe;AAAA;AAAA,WAC9F;AAAA,UAEC,SAAA,oBACC,GAAA;AAAA,YAAC,MAAA;AAAA,YAAA;AAAA,cACC,uCAAA,EAAsC,EAAA;AAAA,cACtC,aAAA,EAAY,MAAA;AAAA,cACZ,SAAA,EAAU,wCAAA;AAAA,cAET,0BAAgB,SAAS;AAAA;AAAA,WAC5B;AAAA,UAID,OAAA,oBACC,GAAA;AAAA,YAAC,MAAA;AAAA,YAAA;AAAA,cACC,uCAAA,EAAsC,EAAA;AAAA,cACtC,SAAA,EAAU,6CAAA;AAAA,cAET,QAAA,EAAA;AAAA;AAAA;AACH;AAAA;AAAA,KAEJ;AAAA,EAEJ;AACF;AAEA,aAAA,CAAc,WAAA,GAAc,eAAA;AAM5B,IAAM,WAAA,GAA6C;AAAA,EACjD,IAAA,EAAM,wCAAA;AAAA,EACN,MAAA,EAAQ,gCAAA;AAAA,EACR,KAAA,EAAO,kCAAA;AAAA,EACP,MAAA,EAAQ;AACV,CAAA;AAwBA,IAAM,eAAA,GAAyD;AAAA,EAC7D,EAAA,EAAI,QAAA;AAAA,EACJ,GAAA,EAAK,QAAA;AAAA,EACL,IAAA,EAAM;AACR,CAAA","file":"chunk-DCVR6NPP.js","sourcesContent":["import { forwardRef } from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport type { ActivityLightProps, ActivityLevel } from \"./ActivityLight.types\";\n\n/**\n * Activity on an **edge** — a relationship between two parties — with a recency\n * ramp and an optional volume.\n *\n * Not a presence dot. `Avatar status` belongs to one party and says whether\n * *they* are online; this belongs to a pair and says when *the link between\n * them* last moved. Use it in a comms column, a collaborator lane, anywhere the\n * subject is the relationship rather than the participant.\n *\n * ## Two kinds of nothing, and they must not look alike\n *\n * - **`level: null`** — the edge is real, its recency is not observable. Renders\n * NOTHING: no dot, no dash, no placeholder. A grey dot would assert \"quiet\",\n * which is a claim we cannot make.\n * - **`count: null`** or omitted — the edge's recency is known but its volume is\n * not. Renders the light with no number.\n * - **`count: 0`** — measured, and none. Renders `0`, because that is a fact.\n * Suppressing it would be indistinguishable from not having looked.\n *\n * The distinction reads as pedantic until a count is wrong in the direction\n * that flatters: a `0` where the truth is \"unknown\" tells the reader an agent\n * works alone, and they believe it.\n *\n * @example\n * ```tsx\n * <ActivityLight level=\"live\" direction=\"both\" count={12} label=\"Genie and Tynn\" />\n * <ActivityLight level=\"quiet\" count={0} label=\"Genie and Weaver\" />\n * <ActivityLight level={null} label=\"Genie and an unknown peer\" />\n * ```\n */\nexport const ActivityLight = forwardRef<HTMLSpanElement, ActivityLightProps>(\n ({ level, direction, count = null, label, className, ...props }, ref) => {\n // The one early return in the component, and the most important line in it.\n if (level === null) return null;\n\n const counted = typeof count === \"number\" && Number.isFinite(count);\n\n return (\n <span\n ref={ref}\n data-react-fancy-activity-light={level}\n {...(direction ? { \"data-react-fancy-activity-light-direction\": direction } : {})}\n aria-label={counted ? `${label}: ${level}, ${count}` : `${label}: ${level}`}\n role=\"img\"\n className={cn(\n \"inline-flex items-center gap-1 align-middle\",\n LEVEL_CLASS[level],\n className,\n )}\n {...props}\n >\n <span\n data-react-fancy-activity-light-dot=\"\"\n aria-hidden=\"true\"\n className={cn(\"size-2 shrink-0 rounded-full bg-current\", level === \"live\" && \"animate-pulse\")}\n />\n\n {direction && (\n <span\n data-react-fancy-activity-light-arrow=\"\"\n aria-hidden=\"true\"\n className=\"font-mono text-[0.625rem] leading-none\"\n >\n {DIRECTION_GLYPH[direction]}\n </span>\n )}\n\n {/* `counted` is the gate, NOT `count` — `0` is falsy and would vanish. */}\n {counted && (\n <span\n data-react-fancy-activity-light-count=\"\"\n className=\"font-mono text-xs tabular-nums leading-none\"\n >\n {count}\n </span>\n )}\n </span>\n );\n },\n);\n\nActivityLight.displayName = \"ActivityLight\";\n\n/**\n * The ramp. Each level is visually distinct from every other — a test asserts\n * that, because two levels that look alike make the ramp a lie.\n */\nconst LEVEL_CLASS: Record<ActivityLevel, string> = {\n live: \"text-emerald-500 dark:text-emerald-400\",\n recent: \"text-sky-500 dark:text-sky-400\",\n quiet: \"text-zinc-400 dark:text-zinc-500\",\n unseen: \"text-amber-500 dark:text-amber-400\",\n};\n\n/**\n * The direction marks, CHOSEN FOR FONT COVERAGE as much as for meaning.\n *\n * `both` was U+21C4 (⇄), which has no coverage in the Liberation fonts — so in\n * a container carrying only those, `in` and `out` drew arrows and `both` drew a\n * tofu box. One of three states failing is worse than all three failing,\n * because the reader cannot tell a font gap from a state: they see two working\n * indicators and one they have no reason to distrust.\n *\n * U+2194 (↔) is in **WGL4**, the repertoire Liberation targets, and that is the\n * line these three sit on — U+2190–2195 are in it, U+21C4 and the whole\n * Dingbats block are not. MEASURED by `claude · tynn` in a Liberation-only\n * container (2026-10-10), rasterising each character and comparing it\n * pixel-for-pixel against a Private Use Area codepoint no font covers: `↔`\n * draws, and U+21C6 `⇆` — the near neighbour, included as a falsifier — does\n * not, which is what makes it evidence of the repertoire rather than of\n * \"Liberation covers arrows\".\n *\n * **So do not \"improve\" `both` back to a prettier arrow.** U+21C4, U+21C6 and\n * the Dingbats (`✓` `✕` `✦`) all need a font many lean images do not ship.\n * A test pins this set.\n */\nconst DIRECTION_GLYPH: Record<\"in\" | \"out\" | \"both\", string> = {\n in: \"←\",\n out: \"→\",\n both: \"↔\",\n};\n"]}
|
|
@@ -2,9 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
var chunkISPCXIJZ_cjs = require('./chunk-ISPCXIJZ.cjs');
|
|
4
4
|
var chunkRMJGF7MN_cjs = require('./chunk-RMJGF7MN.cjs');
|
|
5
|
+
var chunkQKFOJKVE_cjs = require('./chunk-QKFOJKVE.cjs');
|
|
6
|
+
var chunkZU5Q77OT_cjs = require('./chunk-ZU5Q77OT.cjs');
|
|
5
7
|
var react = require('react');
|
|
6
8
|
var jsxRuntime = require('react/jsx-runtime');
|
|
7
9
|
|
|
10
|
+
// src/utils/platform.ts
|
|
11
|
+
var APPLE_UA_DATA_PLATFORMS = /* @__PURE__ */ new Set(["macos", "ios", "ipados"]);
|
|
12
|
+
var APPLE_FREE_TEXT = /\b(mac|macintosh|iphone|ipad|ipod|darwin)/i;
|
|
13
|
+
function detectPlatform(hints) {
|
|
14
|
+
if (!hints) return "generic";
|
|
15
|
+
const uaData = hints.userAgentData?.platform;
|
|
16
|
+
if (typeof uaData === "string" && uaData.length > 0) {
|
|
17
|
+
return APPLE_UA_DATA_PLATFORMS.has(uaData.trim().toLowerCase()) ? "apple" : "generic";
|
|
18
|
+
}
|
|
19
|
+
for (const value of [hints.platform, hints.userAgent]) {
|
|
20
|
+
if (typeof value === "string" && APPLE_FREE_TEXT.test(value)) return "apple";
|
|
21
|
+
}
|
|
22
|
+
return "generic";
|
|
23
|
+
}
|
|
24
|
+
function modifierKeyLabel(platform) {
|
|
25
|
+
return platform === "apple" ? "\u2318" : "Ctrl";
|
|
26
|
+
}
|
|
27
|
+
function browserPlatformHints() {
|
|
28
|
+
if (typeof navigator === "undefined") return null;
|
|
29
|
+
return navigator;
|
|
30
|
+
}
|
|
8
31
|
var DEFAULT_MENTION_COLOR = {
|
|
9
32
|
agent: "#a855f7",
|
|
10
33
|
file: "#10b981",
|
|
@@ -15,12 +38,15 @@ function PromptInput({
|
|
|
15
38
|
commands = [],
|
|
16
39
|
mentions = [],
|
|
17
40
|
showHint = true,
|
|
41
|
+
platform,
|
|
18
42
|
onSubmit,
|
|
19
|
-
placeholder
|
|
43
|
+
placeholder,
|
|
20
44
|
charsPerToken = 4,
|
|
21
45
|
mentionColor,
|
|
22
46
|
maxHeight = 280,
|
|
23
|
-
aboveInput
|
|
47
|
+
aboveInput,
|
|
48
|
+
className,
|
|
49
|
+
...rest
|
|
24
50
|
}) {
|
|
25
51
|
const [text, setText] = react.useState("");
|
|
26
52
|
const [attachments, setAttachments] = react.useState([]);
|
|
@@ -29,6 +55,13 @@ function PromptInput({
|
|
|
29
55
|
const fileRef = react.useRef(null);
|
|
30
56
|
const [dragOver, setDragOver] = react.useState(false);
|
|
31
57
|
const colors = mentionColor ?? DEFAULT_MENTION_COLOR;
|
|
58
|
+
const [detected, setDetected] = react.useState("generic");
|
|
59
|
+
react.useEffect(() => {
|
|
60
|
+
if (platform) return;
|
|
61
|
+
setDetected(detectPlatform(browserPlatformHints()));
|
|
62
|
+
}, [platform]);
|
|
63
|
+
const modKey = modifierKeyLabel(platform ?? detected);
|
|
64
|
+
const resolvedPlaceholder = placeholder ?? `Ask anything. Type / for commands, @ for mentions. ${modKey}+Enter to send.`;
|
|
32
65
|
const tokens = react.useMemo(
|
|
33
66
|
() => Math.ceil(text.length / Math.max(1, charsPerToken)),
|
|
34
67
|
[text, charsPerToken]
|
|
@@ -162,13 +195,20 @@ function PromptInput({
|
|
|
162
195
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
163
196
|
"div",
|
|
164
197
|
{
|
|
198
|
+
...rest,
|
|
199
|
+
"data-react-fancy-prompt-input": "",
|
|
200
|
+
"data-react-fancy-prompt-input-drag": dragOver ? "over" : "idle",
|
|
165
201
|
onDragOver: (e) => {
|
|
166
202
|
e.preventDefault();
|
|
167
203
|
setDragOver(true);
|
|
168
204
|
},
|
|
169
205
|
onDragLeave: () => setDragOver(false),
|
|
170
206
|
onDrop,
|
|
171
|
-
className:
|
|
207
|
+
className: chunkZU5Q77OT_cjs.cn(
|
|
208
|
+
"relative rounded-md border bg-white transition dark:bg-zinc-900",
|
|
209
|
+
dragOver ? "border-violet-400 bg-violet-50/50 dark:border-violet-600 dark:bg-violet-950/30" : "border-zinc-200 dark:border-zinc-800",
|
|
210
|
+
className
|
|
211
|
+
),
|
|
172
212
|
children: [
|
|
173
213
|
aboveInput && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-b border-zinc-200 dark:border-zinc-800", children: aboveInput }),
|
|
174
214
|
attachments.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1.5 border-b border-zinc-200 px-3 py-2 dark:border-zinc-800", children: attachments.map((a) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -176,7 +216,7 @@ function PromptInput({
|
|
|
176
216
|
{
|
|
177
217
|
className: "inline-flex items-center gap-1.5 rounded-md bg-zinc-100 px-2 py-0.5 text-[11px] dark:bg-zinc-800",
|
|
178
218
|
children: [
|
|
179
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
219
|
+
/* @__PURE__ */ jsxRuntime.jsx(chunkQKFOJKVE_cjs.Icon, { name: "paperclip", size: "xs", className: "text-zinc-400" }),
|
|
180
220
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono", children: a.name }),
|
|
181
221
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-zinc-400", children: fmtSize(a.bytes) }),
|
|
182
222
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -201,7 +241,7 @@ function PromptInput({
|
|
|
201
241
|
onChange: (e) => updateText(e.target.value, e.target.selectionStart),
|
|
202
242
|
onKeyDown,
|
|
203
243
|
onPaste,
|
|
204
|
-
placeholder,
|
|
244
|
+
placeholder: resolvedPlaceholder,
|
|
205
245
|
spellCheck: false,
|
|
206
246
|
className: "block w-full resize-none bg-transparent px-3 py-2.5 text-[14px] leading-relaxed outline-none placeholder:text-zinc-400",
|
|
207
247
|
rows: 3
|
|
@@ -267,9 +307,10 @@ function PromptInput({
|
|
|
267
307
|
{
|
|
268
308
|
variant: "ghost",
|
|
269
309
|
size: "sm",
|
|
310
|
+
icon: "paperclip",
|
|
270
311
|
className: "shrink-0",
|
|
271
312
|
onClick: () => fileRef.current?.click(),
|
|
272
|
-
children: "
|
|
313
|
+
children: "attach"
|
|
273
314
|
}
|
|
274
315
|
) }),
|
|
275
316
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -300,16 +341,33 @@ function PromptInput({
|
|
|
300
341
|
] })
|
|
301
342
|
] }),
|
|
302
343
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ml-auto flex shrink-0 items-center gap-2", children: [
|
|
303
|
-
showHint && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
344
|
+
showHint && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
345
|
+
"span",
|
|
346
|
+
{
|
|
347
|
+
className: "hidden text-[10px] text-zinc-500 sm:inline",
|
|
348
|
+
"data-react-fancy-prompt-input-hint": modKey === "\u2318" ? "apple" : "generic",
|
|
349
|
+
children: [
|
|
350
|
+
/* @__PURE__ */ jsxRuntime.jsx("kbd", { className: "rounded border border-zinc-300 bg-zinc-50 px-1 py-0.5 font-mono text-[9px] text-zinc-700 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-300", children: modKey }),
|
|
351
|
+
" ",
|
|
352
|
+
"+",
|
|
353
|
+
" ",
|
|
354
|
+
/* @__PURE__ */ jsxRuntime.jsx("kbd", { className: "rounded border border-zinc-300 bg-zinc-50 px-1 py-0.5 font-mono text-[9px] text-zinc-700 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-300", children: "Enter" }),
|
|
355
|
+
" ",
|
|
356
|
+
"to send"
|
|
357
|
+
]
|
|
358
|
+
}
|
|
359
|
+
),
|
|
360
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
361
|
+
chunkRMJGF7MN_cjs.Button,
|
|
362
|
+
{
|
|
363
|
+
color: "violet",
|
|
364
|
+
size: "sm",
|
|
365
|
+
iconTrailing: "arrow-right",
|
|
366
|
+
className: "shrink-0",
|
|
367
|
+
onClick: submit,
|
|
368
|
+
children: "send"
|
|
369
|
+
}
|
|
370
|
+
)
|
|
313
371
|
] })
|
|
314
372
|
] })
|
|
315
373
|
]
|
|
@@ -327,5 +385,8 @@ function fmtSize(b) {
|
|
|
327
385
|
}
|
|
328
386
|
|
|
329
387
|
exports.PromptInput = PromptInput;
|
|
330
|
-
|
|
331
|
-
|
|
388
|
+
exports.browserPlatformHints = browserPlatformHints;
|
|
389
|
+
exports.detectPlatform = detectPlatform;
|
|
390
|
+
exports.modifierKeyLabel = modifierKeyLabel;
|
|
391
|
+
//# sourceMappingURL=chunk-DNV7MUEE.cjs.map
|
|
392
|
+
//# sourceMappingURL=chunk-DNV7MUEE.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/platform.ts","../src/components/PromptInput/PromptInput.tsx"],"names":["useState","useRef","useEffect","useMemo","jsxs","cn","jsx","Icon","Tooltip","Button"],"mappings":"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","file":"chunk-DNV7MUEE.cjs","sourcesContent":["/**\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 useEffect,\n useMemo,\n useRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { Button } from \"../Button\";\nimport { Icon } from \"../Icon\";\nimport { Tooltip } from \"../Tooltip\";\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\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<HTMLAttributes<HTMLDivElement>, \"onSubmit\" | \"className\"> {\n /** Extra classes on the root surface. MERGED, never replaced. */\n className?: string;\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 /** Called on ⌘/Ctrl+Enter or send button. */\n onSubmit: (text: string, attachments: PromptAttachment[]) => void;\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\nconst DEFAULT_MENTION_COLOR: Record<string, string> = {\n agent: \"#a855f7\",\n file: \"#10b981\",\n person: \"#3b82f6\",\n};\n\nexport function PromptInput({\n budgetTokens,\n commands = [],\n mentions = [],\n showHint = true,\n platform,\n onSubmit,\n placeholder,\n charsPerToken = 4,\n mentionColor,\n maxHeight = 280,\n aboveInput,\n className,\n ...rest\n}: PromptInputProps) {\n const [text, setText] = useState(\"\");\n const [attachments, setAttachments] = useState<PromptAttachment[]>([]);\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 const tokens = useMemo(\n () => Math.ceil(text.length / Math.max(1, charsPerToken)),\n [text, charsPerToken],\n );\n const ratio = Math.min(1, tokens / budgetTokens);\n const meterColor = ratio < 0.6 ? \"#10b981\" : ratio < 0.85 ? \"#f59e0b\" : \"#ef4444\";\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 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 submit = () => {\n if (!text.trim() && attachments.length === 0) return;\n onSubmit(text, attachments);\n setText(\"\");\n setAttachments([]);\n setPicker(null);\n };\n\n const onKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\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 if (e.key === \"Enter\" && !e.metaKey && !e.ctrlKey && 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) return;\n\n setAttachments((cur) => [\n ...cur,\n ...files.map((f) => ({\n id: crypto.randomUUID(),\n name: f.name,\n bytes: f.size,\n file: f,\n type: f.type,\n })),\n ]);\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 onDragOver={(e) => {\n e.preventDefault();\n setDragOver(true);\n }}\n onDragLeave={() => setDragOver(false)}\n onDrop={onDrop}\n className={cn(\n \"relative rounded-md border bg-white transition dark:bg-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 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 className=\"inline-flex items-center gap-1.5 rounded-md bg-zinc-100 px-2 py-0.5 text-[11px] dark:bg-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 setAttachments((cur) => cur.filter((x) => x.id !== a.id))\n }\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 <textarea\n ref={taRef}\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 {picker && items.length > 0 && (\n <div className=\"absolute bottom-full left-2 z-10 mb-1 w-72 overflow-hidden rounded-md border border-zinc-200 bg-white shadow-lg dark:border-zinc-700 dark:bg-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=\"Drop files here, or click\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n icon=\"paperclip\"\n className=\"shrink-0\"\n onClick={() => fileRef.current?.click()}\n >\n 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 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 <div className=\"ml-2 flex min-w-0 items-center gap-1.5 overflow-hidden\">\n <div className=\"h-1.5 w-24 shrink overflow-hidden rounded-full bg-zinc-200 dark:bg-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 <div className=\"ml-auto flex shrink-0 items-center gap-2\">\n {showHint && (\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-zinc-300 bg-zinc-50 px-1 py-0.5 font-mono text-[9px] text-zinc-700 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-300\">\n {modKey}\n </kbd>{\" \"}\n +{\" \"}\n <kbd className=\"rounded border border-zinc-300 bg-zinc-50 px-1 py-0.5 font-mono text-[9px] text-zinc-700 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-300\">\n Enter\n </kbd>{\" \"}\n to send\n </span>\n )}\n <Button\n color=\"violet\"\n size=\"sm\"\n iconTrailing=\"arrow-right\"\n className=\"shrink-0\"\n onClick={submit}\n >\n send\n </Button>\n </div>\n </div>\n </div>\n );\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,8 +1,31 @@
|
|
|
1
1
|
import { Tooltip } from './chunk-35EN5AYE.js';
|
|
2
2
|
import { Button } from './chunk-46IPQEUP.js';
|
|
3
|
-
import {
|
|
3
|
+
import { Icon } from './chunk-IVRYSSM7.js';
|
|
4
|
+
import { cn } from './chunk-MMW2JGKZ.js';
|
|
5
|
+
import { useState, useRef, useEffect, useMemo } from 'react';
|
|
4
6
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
5
7
|
|
|
8
|
+
// src/utils/platform.ts
|
|
9
|
+
var APPLE_UA_DATA_PLATFORMS = /* @__PURE__ */ new Set(["macos", "ios", "ipados"]);
|
|
10
|
+
var APPLE_FREE_TEXT = /\b(mac|macintosh|iphone|ipad|ipod|darwin)/i;
|
|
11
|
+
function detectPlatform(hints) {
|
|
12
|
+
if (!hints) return "generic";
|
|
13
|
+
const uaData = hints.userAgentData?.platform;
|
|
14
|
+
if (typeof uaData === "string" && uaData.length > 0) {
|
|
15
|
+
return APPLE_UA_DATA_PLATFORMS.has(uaData.trim().toLowerCase()) ? "apple" : "generic";
|
|
16
|
+
}
|
|
17
|
+
for (const value of [hints.platform, hints.userAgent]) {
|
|
18
|
+
if (typeof value === "string" && APPLE_FREE_TEXT.test(value)) return "apple";
|
|
19
|
+
}
|
|
20
|
+
return "generic";
|
|
21
|
+
}
|
|
22
|
+
function modifierKeyLabel(platform) {
|
|
23
|
+
return platform === "apple" ? "\u2318" : "Ctrl";
|
|
24
|
+
}
|
|
25
|
+
function browserPlatformHints() {
|
|
26
|
+
if (typeof navigator === "undefined") return null;
|
|
27
|
+
return navigator;
|
|
28
|
+
}
|
|
6
29
|
var DEFAULT_MENTION_COLOR = {
|
|
7
30
|
agent: "#a855f7",
|
|
8
31
|
file: "#10b981",
|
|
@@ -13,12 +36,15 @@ function PromptInput({
|
|
|
13
36
|
commands = [],
|
|
14
37
|
mentions = [],
|
|
15
38
|
showHint = true,
|
|
39
|
+
platform,
|
|
16
40
|
onSubmit,
|
|
17
|
-
placeholder
|
|
41
|
+
placeholder,
|
|
18
42
|
charsPerToken = 4,
|
|
19
43
|
mentionColor,
|
|
20
44
|
maxHeight = 280,
|
|
21
|
-
aboveInput
|
|
45
|
+
aboveInput,
|
|
46
|
+
className,
|
|
47
|
+
...rest
|
|
22
48
|
}) {
|
|
23
49
|
const [text, setText] = useState("");
|
|
24
50
|
const [attachments, setAttachments] = useState([]);
|
|
@@ -27,6 +53,13 @@ function PromptInput({
|
|
|
27
53
|
const fileRef = useRef(null);
|
|
28
54
|
const [dragOver, setDragOver] = useState(false);
|
|
29
55
|
const colors = mentionColor ?? DEFAULT_MENTION_COLOR;
|
|
56
|
+
const [detected, setDetected] = useState("generic");
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (platform) return;
|
|
59
|
+
setDetected(detectPlatform(browserPlatformHints()));
|
|
60
|
+
}, [platform]);
|
|
61
|
+
const modKey = modifierKeyLabel(platform ?? detected);
|
|
62
|
+
const resolvedPlaceholder = placeholder ?? `Ask anything. Type / for commands, @ for mentions. ${modKey}+Enter to send.`;
|
|
30
63
|
const tokens = useMemo(
|
|
31
64
|
() => Math.ceil(text.length / Math.max(1, charsPerToken)),
|
|
32
65
|
[text, charsPerToken]
|
|
@@ -160,13 +193,20 @@ function PromptInput({
|
|
|
160
193
|
return /* @__PURE__ */ jsxs(
|
|
161
194
|
"div",
|
|
162
195
|
{
|
|
196
|
+
...rest,
|
|
197
|
+
"data-react-fancy-prompt-input": "",
|
|
198
|
+
"data-react-fancy-prompt-input-drag": dragOver ? "over" : "idle",
|
|
163
199
|
onDragOver: (e) => {
|
|
164
200
|
e.preventDefault();
|
|
165
201
|
setDragOver(true);
|
|
166
202
|
},
|
|
167
203
|
onDragLeave: () => setDragOver(false),
|
|
168
204
|
onDrop,
|
|
169
|
-
className:
|
|
205
|
+
className: cn(
|
|
206
|
+
"relative rounded-md border bg-white transition dark:bg-zinc-900",
|
|
207
|
+
dragOver ? "border-violet-400 bg-violet-50/50 dark:border-violet-600 dark:bg-violet-950/30" : "border-zinc-200 dark:border-zinc-800",
|
|
208
|
+
className
|
|
209
|
+
),
|
|
170
210
|
children: [
|
|
171
211
|
aboveInput && /* @__PURE__ */ jsx("div", { className: "border-b border-zinc-200 dark:border-zinc-800", children: aboveInput }),
|
|
172
212
|
attachments.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5 border-b border-zinc-200 px-3 py-2 dark:border-zinc-800", children: attachments.map((a) => /* @__PURE__ */ jsxs(
|
|
@@ -174,7 +214,7 @@ function PromptInput({
|
|
|
174
214
|
{
|
|
175
215
|
className: "inline-flex items-center gap-1.5 rounded-md bg-zinc-100 px-2 py-0.5 text-[11px] dark:bg-zinc-800",
|
|
176
216
|
children: [
|
|
177
|
-
/* @__PURE__ */ jsx("
|
|
217
|
+
/* @__PURE__ */ jsx(Icon, { name: "paperclip", size: "xs", className: "text-zinc-400" }),
|
|
178
218
|
/* @__PURE__ */ jsx("span", { className: "font-mono", children: a.name }),
|
|
179
219
|
/* @__PURE__ */ jsx("span", { className: "text-zinc-400", children: fmtSize(a.bytes) }),
|
|
180
220
|
/* @__PURE__ */ jsx(
|
|
@@ -199,7 +239,7 @@ function PromptInput({
|
|
|
199
239
|
onChange: (e) => updateText(e.target.value, e.target.selectionStart),
|
|
200
240
|
onKeyDown,
|
|
201
241
|
onPaste,
|
|
202
|
-
placeholder,
|
|
242
|
+
placeholder: resolvedPlaceholder,
|
|
203
243
|
spellCheck: false,
|
|
204
244
|
className: "block w-full resize-none bg-transparent px-3 py-2.5 text-[14px] leading-relaxed outline-none placeholder:text-zinc-400",
|
|
205
245
|
rows: 3
|
|
@@ -265,9 +305,10 @@ function PromptInput({
|
|
|
265
305
|
{
|
|
266
306
|
variant: "ghost",
|
|
267
307
|
size: "sm",
|
|
308
|
+
icon: "paperclip",
|
|
268
309
|
className: "shrink-0",
|
|
269
310
|
onClick: () => fileRef.current?.click(),
|
|
270
|
-
children: "
|
|
311
|
+
children: "attach"
|
|
271
312
|
}
|
|
272
313
|
) }),
|
|
273
314
|
/* @__PURE__ */ jsx(
|
|
@@ -298,16 +339,33 @@ function PromptInput({
|
|
|
298
339
|
] })
|
|
299
340
|
] }),
|
|
300
341
|
/* @__PURE__ */ jsxs("div", { className: "ml-auto flex shrink-0 items-center gap-2", children: [
|
|
301
|
-
showHint && /* @__PURE__ */ jsxs(
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
342
|
+
showHint && /* @__PURE__ */ jsxs(
|
|
343
|
+
"span",
|
|
344
|
+
{
|
|
345
|
+
className: "hidden text-[10px] text-zinc-500 sm:inline",
|
|
346
|
+
"data-react-fancy-prompt-input-hint": modKey === "\u2318" ? "apple" : "generic",
|
|
347
|
+
children: [
|
|
348
|
+
/* @__PURE__ */ jsx("kbd", { className: "rounded border border-zinc-300 bg-zinc-50 px-1 py-0.5 font-mono text-[9px] text-zinc-700 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-300", children: modKey }),
|
|
349
|
+
" ",
|
|
350
|
+
"+",
|
|
351
|
+
" ",
|
|
352
|
+
/* @__PURE__ */ jsx("kbd", { className: "rounded border border-zinc-300 bg-zinc-50 px-1 py-0.5 font-mono text-[9px] text-zinc-700 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-300", children: "Enter" }),
|
|
353
|
+
" ",
|
|
354
|
+
"to send"
|
|
355
|
+
]
|
|
356
|
+
}
|
|
357
|
+
),
|
|
358
|
+
/* @__PURE__ */ jsx(
|
|
359
|
+
Button,
|
|
360
|
+
{
|
|
361
|
+
color: "violet",
|
|
362
|
+
size: "sm",
|
|
363
|
+
iconTrailing: "arrow-right",
|
|
364
|
+
className: "shrink-0",
|
|
365
|
+
onClick: submit,
|
|
366
|
+
children: "send"
|
|
367
|
+
}
|
|
368
|
+
)
|
|
311
369
|
] })
|
|
312
370
|
] })
|
|
313
371
|
]
|
|
@@ -324,6 +382,6 @@ function fmtSize(b) {
|
|
|
324
382
|
return `${(b / 1024 / 1024).toFixed(1)} MB`;
|
|
325
383
|
}
|
|
326
384
|
|
|
327
|
-
export { PromptInput };
|
|
328
|
-
//# sourceMappingURL=chunk-
|
|
329
|
-
//# sourceMappingURL=chunk-
|
|
385
|
+
export { PromptInput, browserPlatformHints, detectPlatform, modifierKeyLabel };
|
|
386
|
+
//# sourceMappingURL=chunk-O3WEV562.js.map
|
|
387
|
+
//# sourceMappingURL=chunk-O3WEV562.js.map
|