@kanzo-tech/ai 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-mark.d.ts +1 -1
- package/dist/ai-mark.d.ts.map +1 -1
- package/dist/ai-mark.js +12 -12
- package/dist/ai-mark.js.map +1 -1
- package/dist/assist.d.ts +71 -0
- package/dist/assist.d.ts.map +1 -0
- package/dist/assist.js +394 -0
- package/dist/assist.js.map +1 -0
- package/dist/chat.d.ts +41 -0
- package/dist/chat.d.ts.map +1 -0
- package/dist/chat.js +77 -0
- package/dist/chat.js.map +1 -0
- package/dist/engine.d.ts +101 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +197 -0
- package/dist/engine.js.map +1 -0
- package/dist/index.d.ts +7 -24
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -56
- package/dist/index.js.map +1 -1
- package/dist/message.js +25 -66
- package/dist/message.js.map +1 -1
- package/dist/prompt-input.d.ts +2 -2
- package/dist/prompt-input.d.ts.map +1 -1
- package/dist/prompt-input.js +47 -49
- package/dist/prompt-input.js.map +1 -1
- package/dist/proposals.d.ts +28 -0
- package/dist/proposals.d.ts.map +1 -0
- package/dist/proposals.js +56 -0
- package/dist/proposals.js.map +1 -0
- package/dist/reasoning.d.ts +4 -3
- package/dist/reasoning.d.ts.map +1 -1
- package/dist/reasoning.js +60 -55
- package/dist/reasoning.js.map +1 -1
- package/dist/tool.d.ts +25 -50
- package/dist/tool.d.ts.map +1 -1
- package/dist/tool.js +83 -95
- package/dist/tool.js.map +1 -1
- package/package.json +18 -34
- package/dist/complete.d.ts +0 -87
- package/dist/complete.d.ts.map +0 -1
- package/dist/complete.js +0 -278
- package/dist/complete.js.map +0 -1
- package/dist/message-part.d.ts +0 -122
- package/dist/message-part.d.ts.map +0 -1
- package/dist/message-part.js +0 -8
- package/dist/message-part.js.map +0 -1
- package/dist/message-text.d.ts +0 -44
- package/dist/message-text.d.ts.map +0 -1
- package/dist/message-text.js +0 -49
- package/dist/message-text.js.map +0 -1
- package/dist/suggest.d.ts +0 -82
- package/dist/suggest.d.ts.map +0 -1
- package/dist/suggest.js +0 -123
- package/dist/suggest.js.map +0 -1
- package/dist/task.d.ts +0 -48
- package/dist/task.d.ts.map +0 -1
- package/dist/task.js +0 -91
- package/dist/task.js.map +0 -1
- package/dist/types.d.ts +0 -10
- package/dist/types.d.ts.map +0 -1
- package/dist/use-ai.d.ts +0 -170
- package/dist/use-ai.d.ts.map +0 -1
- package/dist/use-ai.js +0 -186
- package/dist/use-ai.js.map +0 -1
package/dist/message-part.js
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
const s = (t) => t.type === "text", o = (t) => t.type === "reasoning", e = (t) => t.type === "tool", a = (t) => t.type === "task";
|
|
2
|
-
export {
|
|
3
|
-
o as isReasoningPart,
|
|
4
|
-
a as isTaskPart,
|
|
5
|
-
s as isTextPart,
|
|
6
|
-
e as isToolPart
|
|
7
|
-
};
|
|
8
|
-
//# sourceMappingURL=message-part.js.map
|
package/dist/message-part.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"message-part.js","sources":["../src/message-part.ts"],"sourcesContent":["import type { MessageRole } from \"./message.js\";\nimport type { RunState } from \"./task.js\";\n\n/**\n * A turn as a **list of parts**, and the join every component in this package was missing.\n *\n * `Message`, `MessageText`, `Reasoning`, `Tool` and `Task` were each correct and none of them was\n * wired to the next: a host held its own shape and translated into five sets of props by hand.\n * keasy's `AskMessage` is the worked example — it carries `sql`, `reasoning`, `explanation` and a\n * `phase` of `'generating' | 'executing' | 'explaining' | 'done'`, which is a tool part, a reasoning\n * part, a text part and a state, hand-rolled. **That is the second consumer, and it existed before\n * this file did.** So this is not a protocol invented ahead of its need.\n *\n * The shape mirrors the AI SDK's `UIMessage`, deliberately and without depending on it — the four\n * properties that make theirs work are the four copied here:\n *\n * 1. **A message is a list of parts, not a string.** One `.map` with one `switch` on `part.type`\n * renders a whole turn.\n * 2. **State lives on the part**, so a renderer is told what to draw rather than inferring it.\n * 3. **Generic in metadata and tools**, so a product extends the union instead of forking it.\n * 4. **Transport stays separate.** Ours already is, and is smaller than theirs: a caller passes a\n * function returning an `AsyncIterable`. Nothing here replaces that seam.\n *\n * ## Four variants, not nine\n *\n * Theirs has nine. `file`, `data-*`, `step-start` and `dynamic-tool` have no call site here, and\n * `source` is **declined rather than deferred**: discovery does not cite a document, it runs a\n * query, and provenance that is a statement plus rows is a tool call. It arrives the day an answer\n * rests on a text somebody wrote.\n *\n * ## The two state vocabularies are the ones already in the package\n *\n * `RunState` — `pending` · `running` · `done` · `failed` — is `Task`'s and `Tool`'s, from one table\n * with one label map and one icon family. It is not restated here, and the reference's\n * `input-streaming | input-available | output-available | output-error` is not adopted: four states\n * under two spellings is the drift this repo keeps deleting.\n *\n * `StreamState` is the other one, and it is two values because text and reasoning have two.\n *\n * ## What this file is not\n *\n * **It renders nothing.** There is no `<Message parts={…} />` here and that is the shape rather than\n * an omission: which part a product renders, in what order, with what chrome around it, is the\n * thing that differs per product — and a component that owned the `switch` would own those answers\n * too. The `switch` belongs to the host; what this gives it is a union exhaustive enough that\n * TypeScript tells the host when a case is missing.\n */\n\n/** Where a streamed part has got to. Two values, because text and reasoning have two. */\nexport type StreamState = \"streaming\" | \"done\";\n\n/** Prose the model wrote. `MessageText` draws it. */\nexport interface AiTextPart {\n type: \"text\";\n text: string;\n state?: StreamState;\n}\n\n/**\n * The model's own working, folded away. `Reasoning` draws it.\n *\n * **No duration here**, and that is checked rather than assumed: `Reasoning` times itself off the\n * `streaming` transition and renders *Thought for N seconds* from its own state. A `seconds` on the\n * part would be a second measurement of one fact, and the one no component reads.\n */\nexport interface AiReasoningPart {\n type: \"reasoning\";\n text: string;\n state?: StreamState;\n}\n\n/**\n * One call the model made. `Tool` draws it.\n *\n * `input` and `output` are `unknown` here and **children** at the component: `ToolInput` and\n * `ToolOutput` take what you render rather than a JSON blob, because we always know what the tool\n * was. The part still carries the values, because a host that wants the JSON tree has to have them.\n *\n * `NAME` is the axis a product extends along. Give it a union of your tool names and a `switch` on\n * `part.name` narrows `input` and `output` if you declare them per name.\n */\nexport interface AiToolPart<NAME extends string = string> {\n type: \"tool\";\n name: NAME;\n state?: RunState;\n input?: unknown;\n output?: unknown;\n /** Set when `state` is `failed`. */\n errorText?: string;\n}\n\n/**\n * One step of work, ours and with no equivalent in the reference. `Task` draws it.\n *\n * Two fields, because `Task` has two parts: `TaskTitle` takes the words and `TaskStatus` reads the\n * state off the root and says it. There is no list of sub-lines on the part because there is none\n * on the component — a step that wants children is a step whose host renders them.\n */\nexport interface AiTaskPart {\n type: \"task\";\n title: string;\n state?: RunState;\n}\n\nexport type AiMessagePart<NAME extends string = string> =\n | AiTextPart\n | AiReasoningPart\n | AiToolPart<NAME>\n | AiTaskPart;\n\n/**\n * One turn.\n *\n * `METADATA` is where a product hangs what only it knows — a request id, a cost, who asked. It is\n * unconstrained on purpose: constraining it is how a transport type ends up describing one product.\n */\nexport interface AiMessage<METADATA = unknown, NAME extends string = string> {\n id: string;\n role: MessageRole;\n metadata?: METADATA;\n parts: AiMessagePart<NAME>[];\n}\n\n/**\n * The guards, because they are what make a host's `switch` readable.\n *\n * Each is a type predicate, so `parts.filter(isToolPart)` is an `AiToolPart[]` and not a cast. That\n * is the whole reason they exist rather than the host writing `part.type === \"tool\"`: the equality\n * narrows inside a `switch` and does not survive a `.filter`.\n */\nexport const isTextPart = (part: AiMessagePart): part is AiTextPart => part.type === \"text\";\n\nexport const isReasoningPart = (part: AiMessagePart): part is AiReasoningPart =>\n part.type === \"reasoning\";\n\nexport const isToolPart = <NAME extends string>(\n part: AiMessagePart<NAME>,\n): part is AiToolPart<NAME> => part.type === \"tool\";\n\nexport const isTaskPart = (part: AiMessagePart): part is AiTaskPart => part.type === \"task\";\n"],"names":["isTextPart","part","isReasoningPart","isToolPart","isTaskPart"],"mappings":"AAkIO,MAAMA,IAAa,CAACC,MAA4CA,EAAK,SAAS,QAExEC,IAAkB,CAACD,MAC9BA,EAAK,SAAS,aAEHE,IAAa,CACxBF,MAC6BA,EAAK,SAAS,QAEhCG,IAAa,CAACH,MAA4CA,EAAK,SAAS;"}
|
package/dist/message-text.d.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { ark } from '@ark-ui/react/factory';
|
|
2
|
-
import * as React from "react";
|
|
3
|
-
/**
|
|
4
|
-
* A streamed answer, arriving a word at a time.
|
|
5
|
-
*
|
|
6
|
-
* The shape is Streamdown's, which is what AI Elements renders an answer and a reasoning block
|
|
7
|
-
* with — `packages/streamdown/lib/animate.ts` and its `styles.css`. Four things are taken from it
|
|
8
|
-
* and each one is a bug this component would otherwise have:
|
|
9
|
-
*
|
|
10
|
-
* 1. **A word, not a chunk.** A model emits tokens and tokens cut words in half; animating what
|
|
11
|
-
* arrives makes half a word fade in and the other half a frame later. Streamdown splits the
|
|
12
|
-
* same way and glues the trailing whitespace onto the word before it, so a partial answer never
|
|
13
|
-
* ends on a naked space either.
|
|
14
|
-
* 2. **Only what is new animates.** Words already on screen are not re-run. Here that falls out of
|
|
15
|
-
* the key: each word is keyed by its absolute character offset, so React keeps the elements it
|
|
16
|
-
* already mounted. Keyed by the word itself, the last one remounts on every frame while its own
|
|
17
|
-
* characters are still arriving — its arrival restarts each time and it flashes instead of
|
|
18
|
-
* settling. The text only ever grows at the end, which is what makes the offset stable.
|
|
19
|
-
* 3. **A stagger, so a batch cascades instead of flashing.** A stream does not deliver one word per
|
|
20
|
-
* frame; it delivers twenty at once and then nothing for 200ms. Without a per-word delay the
|
|
21
|
-
* twenty fade in together, which is a flash — the thing the animation exists to avoid.
|
|
22
|
-
* 4. **A budget on the cascade.** {@link MAX_CASCADE} is Streamdown's `MAX_ANIMATION_BACKLOG_MS`.
|
|
23
|
-
* A stagger applied naively to a fast stream schedules words further and further ahead of the
|
|
24
|
-
* text, so an unbounded queue of `opacity: 0` words builds up behind a stream that has already
|
|
25
|
-
* finished. Compressing the step to fit the budget — floored at {@link MIN_STEP}, so the batch
|
|
26
|
-
* still cascades rather than collapsing to zero — is what keeps the paint caught up.
|
|
27
|
-
*
|
|
28
|
-
* What is NOT taken, and it is the gap to know about: Streamdown exists mostly to render
|
|
29
|
-
* **incomplete markdown**, closing an unterminated `**bold` or a half-arrived code fence so the
|
|
30
|
-
* answer does not flicker as it completes. This takes a plain string and renders it as text, which
|
|
31
|
-
* has no such problem and also no markdown. A source that streams markdown into this will want
|
|
32
|
-
* that, and it is a dependency rather than a rewrite.
|
|
33
|
-
*
|
|
34
|
-
* Reduced motion is `motion-reduce:animate-none`: with the animation off the words are already at
|
|
35
|
-
* their settled opacity, so there is nothing to unwind.
|
|
36
|
-
*/
|
|
37
|
-
export interface MessageTextProps extends Omit<React.ComponentProps<typeof ark.div>, "children"> {
|
|
38
|
-
/** The answer so far. Grows; never rewritten. */
|
|
39
|
-
children: string;
|
|
40
|
-
/** More is coming. Draws the caret, and nothing else — the arrival is per word. */
|
|
41
|
-
streaming?: boolean;
|
|
42
|
-
}
|
|
43
|
-
export declare const MessageText: (props: MessageTextProps) => React.JSX.Element;
|
|
44
|
-
//# sourceMappingURL=message-text.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"message-text.d.ts","sourceRoot":"","sources":["../src/message-text.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,GAAG,EAAE,MAAM,uBAAuB,CAAC;AAC5C,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,WAAW,gBACf,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,GAAG,CAAC,GAAG,CAAC,EAAE,UAAU,CAAC;IAC9D,iDAAiD;IACjD,QAAQ,EAAE,MAAM,CAAC;IACjB,mFAAmF;IACnF,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAYD,eAAO,MAAM,WAAW,GAAI,OAAO,gBAAgB,sBAsDlD,CAAC"}
|
package/dist/message-text.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { jsxs as M, jsx as l } from "react/jsx-runtime";
|
|
3
|
-
import { ark as v } from "@ark-ui/react/factory";
|
|
4
|
-
import * as d from "react";
|
|
5
|
-
import { cn as r } from "@kanzo-tech/ui";
|
|
6
|
-
const w = /\S+\s*/g, h = 18, A = 320, N = 4, b = (p) => {
|
|
7
|
-
const { children: a, streaming: n = !1, className: u, slot: g, ...f } = p, o = d.useRef(n ? 0 : a.length), s = [...a.matchAll(w)], e = s.findIndex((t) => t.index >= o.current), i = e < 0 ? 0 : s.length - e, x = i > 1 ? Math.max(N, Math.min(h, A / (i - 1))) : h;
|
|
8
|
-
return d.useLayoutEffect(() => {
|
|
9
|
-
o.current = a.length;
|
|
10
|
-
}), /* @__PURE__ */ M(
|
|
11
|
-
v.div,
|
|
12
|
-
{
|
|
13
|
-
className: r("min-w-0 whitespace-pre-wrap break-words", u),
|
|
14
|
-
"data-streaming": n || void 0,
|
|
15
|
-
...f,
|
|
16
|
-
"data-slot": g ?? "message-text",
|
|
17
|
-
children: [
|
|
18
|
-
s.map((t, m) => {
|
|
19
|
-
const c = e >= 0 && m >= e;
|
|
20
|
-
return /* @__PURE__ */ l(
|
|
21
|
-
"span",
|
|
22
|
-
{
|
|
23
|
-
className: r(c && "animate-arrive motion-reduce:animate-none"),
|
|
24
|
-
style: c ? { animationDelay: `${Math.round((m - e) * x)}ms` } : void 0,
|
|
25
|
-
children: t[0]
|
|
26
|
-
},
|
|
27
|
-
t.index
|
|
28
|
-
);
|
|
29
|
-
}),
|
|
30
|
-
/* @__PURE__ */ l(
|
|
31
|
-
"span",
|
|
32
|
-
{
|
|
33
|
-
"aria-hidden": !0,
|
|
34
|
-
className: r(
|
|
35
|
-
"ms-0.5 inline-block h-[1em] w-[0.5ch] align-[-0.1em] bg-current",
|
|
36
|
-
"animate-pulse motion-reduce:animate-none",
|
|
37
|
-
!n && "hidden"
|
|
38
|
-
),
|
|
39
|
-
"data-slot": "message-caret"
|
|
40
|
-
}
|
|
41
|
-
)
|
|
42
|
-
]
|
|
43
|
-
}
|
|
44
|
-
);
|
|
45
|
-
};
|
|
46
|
-
export {
|
|
47
|
-
b as MessageText
|
|
48
|
-
};
|
|
49
|
-
//# sourceMappingURL=message-text.js.map
|
package/dist/message-text.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"message-text.js","sources":["../src/message-text.tsx"],"sourcesContent":["\"use client\";\n\nimport { ark } from \"@ark-ui/react/factory\";\nimport * as React from \"react\";\nimport { cn } from \"@kanzo-tech/ui\";\n\n/**\n * A streamed answer, arriving a word at a time.\n *\n * The shape is Streamdown's, which is what AI Elements renders an answer and a reasoning block\n * with — `packages/streamdown/lib/animate.ts` and its `styles.css`. Four things are taken from it\n * and each one is a bug this component would otherwise have:\n *\n * 1. **A word, not a chunk.** A model emits tokens and tokens cut words in half; animating what\n * arrives makes half a word fade in and the other half a frame later. Streamdown splits the\n * same way and glues the trailing whitespace onto the word before it, so a partial answer never\n * ends on a naked space either.\n * 2. **Only what is new animates.** Words already on screen are not re-run. Here that falls out of\n * the key: each word is keyed by its absolute character offset, so React keeps the elements it\n * already mounted. Keyed by the word itself, the last one remounts on every frame while its own\n * characters are still arriving — its arrival restarts each time and it flashes instead of\n * settling. The text only ever grows at the end, which is what makes the offset stable.\n * 3. **A stagger, so a batch cascades instead of flashing.** A stream does not deliver one word per\n * frame; it delivers twenty at once and then nothing for 200ms. Without a per-word delay the\n * twenty fade in together, which is a flash — the thing the animation exists to avoid.\n * 4. **A budget on the cascade.** {@link MAX_CASCADE} is Streamdown's `MAX_ANIMATION_BACKLOG_MS`.\n * A stagger applied naively to a fast stream schedules words further and further ahead of the\n * text, so an unbounded queue of `opacity: 0` words builds up behind a stream that has already\n * finished. Compressing the step to fit the budget — floored at {@link MIN_STEP}, so the batch\n * still cascades rather than collapsing to zero — is what keeps the paint caught up.\n *\n * What is NOT taken, and it is the gap to know about: Streamdown exists mostly to render\n * **incomplete markdown**, closing an unterminated `**bold` or a half-arrived code fence so the\n * answer does not flicker as it completes. This takes a plain string and renders it as text, which\n * has no such problem and also no markdown. A source that streams markdown into this will want\n * that, and it is a dependency rather than a rewrite.\n *\n * Reduced motion is `motion-reduce:animate-none`: with the animation off the words are already at\n * their settled opacity, so there is nothing to unwind.\n */\nexport interface MessageTextProps\n extends Omit<React.ComponentProps<typeof ark.div>, \"children\"> {\n /** The answer so far. Grows; never rewritten. */\n children: string;\n /** More is coming. Draws the caret, and nothing else — the arrival is per word. */\n streaming?: boolean;\n}\n\n/** A run of non-space plus the space that follows it, so the whitespace travels with its word. */\nconst WORD = /\\S+\\s*/g;\n\n/** Milliseconds between two words of one batch, before the budget below compresses it. */\nconst STAGGER = 18;\n/** How far ahead of the text a batch may schedule. Streamdown's `MAX_ANIMATION_BACKLOG_MS`. */\nconst MAX_CASCADE = 320;\n/** The step never falls below this, so a compressed batch still cascades. Streamdown's floor. */\nconst MIN_STEP = 4;\n\nexport const MessageText = (props: MessageTextProps) => {\n const { children, streaming = false, className, slot, ...rest } = props;\n\n // What was on screen when the last render committed. **A message that mounts complete did not\n // arrive**, so one that is not streaming at mount starts with all of it settled and animates\n // nothing — a transcript loaded from history should not replay itself.\n const settled = React.useRef(streaming ? 0 : children.length);\n\n const words = [...children.matchAll(WORD)];\n const from = words.findIndex((word) => word.index >= settled.current);\n const fresh = from < 0 ? 0 : words.length - from;\n const step =\n fresh > 1 ? Math.max(MIN_STEP, Math.min(STAGGER, MAX_CASCADE / (fresh - 1))) : STAGGER;\n\n // After paint, not during render: a ref written in the render body is wrong under StrictMode's\n // double invoke, which is the same hazard Streamdown answers with `mark`/`rewind`.\n React.useLayoutEffect(() => {\n settled.current = children.length;\n });\n\n return (\n <ark.div\n className={cn(\"min-w-0 whitespace-pre-wrap break-words\", className)}\n data-streaming={streaming || undefined}\n {...rest}\n data-slot={slot ?? \"message-text\"}\n >\n {words.map((word, index) => {\n const arriving = from >= 0 && index >= from;\n return (\n <span\n className={cn(arriving && \"animate-arrive motion-reduce:animate-none\")}\n key={word.index}\n style={arriving ? { animationDelay: `${Math.round((index - from) * step)}ms` } : undefined}\n >\n {word[0]}\n </span>\n );\n })}\n {/* Always rendered, hidden rather than unmounted: a component that renders one shape while\n streaming and another when done re-mounts every word at the moment it finishes, and the\n whole answer arrives a second time. `aria-hidden` because a caret is not content — the\n live region that announces the turn is the caller's. */}\n <span\n aria-hidden\n className={cn(\n \"ms-0.5 inline-block h-[1em] w-[0.5ch] align-[-0.1em] bg-current\",\n \"animate-pulse motion-reduce:animate-none\",\n !streaming && \"hidden\",\n )}\n data-slot=\"message-caret\"\n />\n </ark.div>\n );\n};\n"],"names":[],"mappings":";;;;;AAiDA;AAUE;AAeA;AACE;AAA2B;AAI3B;AAAK;AAAJ;AACmE;AACrC;AACzB;AACe;AAElB;AACC;AACA;AACE;AAAC;AAAA;AACsE;AAEY;AAE1E;AAAA;AAHG;AAAA;AAMf;AAKD;AAAC;AAAA;AACY;AACA;AACT;AACA;AACc;AAAA;AAEN;AAAA;AAAA;AACZ;AAAA;AAGN;;;;"}
|
package/dist/suggest.d.ts
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { ark } from '@ark-ui/react/factory';
|
|
2
|
-
import { Suggestions } from '@kanzo-tech/ui';
|
|
3
|
-
import { AiMarkProps } from './ai-mark.js';
|
|
4
|
-
import { Candidate } from './types.js';
|
|
5
|
-
import * as React from "react";
|
|
6
|
-
/** When the source is asked. */
|
|
7
|
-
export type SuggestTrigger =
|
|
8
|
-
/** On a press of the ✨. Nothing is requested until somebody asks for it. */
|
|
9
|
-
"press"
|
|
10
|
-
/** As soon as the field takes focus. Prefetches, and bills a model for a tab-through. */
|
|
11
|
-
| "focus";
|
|
12
|
-
export interface SuggestRootProps extends Omit<React.ComponentProps<typeof ark.div>, "onSelect"> {
|
|
13
|
-
/** The source. Yields candidates; honour the `AbortSignal`. */
|
|
14
|
-
suggest: (signal?: AbortSignal) => AsyncIterable<Candidate>;
|
|
15
|
-
/** What the field already holds — these are never offered back. */
|
|
16
|
-
existing?: string[];
|
|
17
|
-
/** Where a chosen value goes. */
|
|
18
|
-
onPick: (value: string) => void;
|
|
19
|
-
/**
|
|
20
|
-
* How many to take from the stream.
|
|
21
|
-
*
|
|
22
|
-
* @default 6
|
|
23
|
-
*/
|
|
24
|
-
limit?: number;
|
|
25
|
-
/**
|
|
26
|
-
* @default "press"
|
|
27
|
-
*/
|
|
28
|
-
trigger?: SuggestTrigger;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Candidate values for one field, offered **in the flow underneath it**.
|
|
32
|
-
*
|
|
33
|
-
* This replaced a popover, and the popover is the whole story. Hiding the candidates behind a door
|
|
34
|
-
* bought nothing and cost six workarounds, every one of them downstream of the door and not of the
|
|
35
|
-
* candidates: a non-modal popover, because a modal one put `pointer-events: none` on the very field
|
|
36
|
-
* the values were for; a `data-autofocus`, because a popover has to hand focus somewhere; a
|
|
37
|
-
* `Delete`/`Backspace` handler, because a button inside `role="option"` is unreachable by Tab; a
|
|
38
|
-
* shared label id, because a popover names itself from a title and a listbox from a label; a ✕ that
|
|
39
|
-
* had to be quieted because an offer looked identical to a committed tag; and a three-row window
|
|
40
|
-
* with a refill loop in the hook to keep it full. None of them exist here.
|
|
41
|
-
*
|
|
42
|
-
* **Focus decides visibility, a press decides billing**, and they are deliberately not the same
|
|
43
|
-
* gesture. The strip appears while the field has focus and there is something to show — which is
|
|
44
|
-
* what keeps a form of eleven fields from becoming a wall, since only one field is focused at a
|
|
45
|
-
* time. Asking is `trigger`, and it defaults to a press: a source that fires on focus bills a model
|
|
46
|
-
* for a tab-through, and that is a decision a caller should make on purpose.
|
|
47
|
-
*/
|
|
48
|
-
export declare function SuggestRoot(props: SuggestRootProps): React.JSX.Element;
|
|
49
|
-
/**
|
|
50
|
-
* The ✨ — **a mark, not a door.**
|
|
51
|
-
*
|
|
52
|
-
* At rest it says the field is model-assisted, which a reader can see without touching anything.
|
|
53
|
-
* Pressing it asks. It no longer opens or closes a layer, so there is no `data-state` to light it
|
|
54
|
-
* from and nothing to trap focus in.
|
|
55
|
-
*/
|
|
56
|
-
export declare function SuggestMark(props: Omit<AiMarkProps, "busy" | "offering"> & {
|
|
57
|
-
/**
|
|
58
|
-
* The accessible name while candidates are on offer, when `label` names the mark at rest.
|
|
59
|
-
*
|
|
60
|
-
* @default "Suggest different values"
|
|
61
|
-
*/
|
|
62
|
-
offeringLabel?: string;
|
|
63
|
-
}): React.JSX.Element;
|
|
64
|
-
export interface SuggestListProps extends React.ComponentProps<typeof Suggestions> {
|
|
65
|
-
/** Shown while the source is answering and has produced nothing yet. */
|
|
66
|
-
pending?: React.ReactNode;
|
|
67
|
-
/** Shown when the source finished with nothing to offer. */
|
|
68
|
-
empty?: React.ReactNode;
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* The strip, and the only part that decides whether anything is on screen.
|
|
72
|
-
*
|
|
73
|
-
* It renders `Suggestions` from `@kanzo-tech/ui` — a row of buttons that knows nothing about a
|
|
74
|
-
* model — so everything model-shaped stays on this side of the boundary: the ✨, the engine, and
|
|
75
|
-
* the rationale that comes back with a candidate.
|
|
76
|
-
*
|
|
77
|
-
* The rationale is the highlighted pill's `title` rather than a line under the strip. The line was
|
|
78
|
-
* tried and it moved the layout on every hover; a `title` is the browser's own tooltip, reaches the
|
|
79
|
-
* keyboard through focus, and costs no box.
|
|
80
|
-
*/
|
|
81
|
-
export declare function SuggestList(props: SuggestListProps): React.JSX.Element | null;
|
|
82
|
-
//# sourceMappingURL=suggest.d.ts.map
|
package/dist/suggest.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"suggest.d.ts","sourceRoot":"","sources":["../src/suggest.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,GAAG,EAAE,MAAM,uBAAuB,CAAC;AAC5C,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAgD,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC3F,OAAO,EAAU,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAIxD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAqB5C,gCAAgC;AAChC,MAAM,MAAM,cAAc;AACxB,4EAA4E;AAC1E,OAAO;AACT,yFAAyF;GACvF,OAAO,CAAC;AAEZ,MAAM,WAAW,gBACf,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,GAAG,CAAC,GAAG,CAAC,EAAE,UAAU,CAAC;IAC9D,+DAA+D;IAC/D,OAAO,EAAE,CAAC,MAAM,CAAC,EAAE,WAAW,KAAK,aAAa,CAAC,SAAS,CAAC,CAAC;IAC5D,mEAAmE;IACnE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,iCAAiC;IACjC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,OAAO,CAAC,EAAE,cAAc,CAAC;CAC1B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,qBA2ElD;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,UAAU,CAAC,GAAG;IAC9C;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,qBAoBF;AAED,MAAM,WAAW,gBAAiB,SAAQ,KAAK,CAAC,cAAc,CAAC,OAAO,WAAW,CAAC;IAChF,wEAAwE;IACxE,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,4DAA4D;IAC5D,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CACzB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,4BAiElD"}
|
package/dist/suggest.js
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { jsx as r, jsxs as x } from "react/jsx-runtime";
|
|
3
|
-
import { XIcon as w } from "lucide-react";
|
|
4
|
-
import { ark as d } from "@ark-ui/react/factory";
|
|
5
|
-
import * as f from "react";
|
|
6
|
-
import { ButtonGroup as L, Suggestion as M, Button as P, Spinner as R, Suggestions as T, cn as h } from "@kanzo-tech/ui";
|
|
7
|
-
import { AiMark as j } from "./ai-mark.js";
|
|
8
|
-
import { useSuggestions as A } from "./use-ai.js";
|
|
9
|
-
const S = f.createContext(null), k = (o) => {
|
|
10
|
-
const n = f.useContext(S);
|
|
11
|
-
if (!n) throw new Error(`${o} must render inside <SuggestRoot>`);
|
|
12
|
-
return n;
|
|
13
|
-
};
|
|
14
|
-
function I(o) {
|
|
15
|
-
const {
|
|
16
|
-
suggest: n,
|
|
17
|
-
existing: c,
|
|
18
|
-
onPick: l,
|
|
19
|
-
limit: m,
|
|
20
|
-
trigger: a = "press",
|
|
21
|
-
children: t,
|
|
22
|
-
className: u,
|
|
23
|
-
onBlur: e,
|
|
24
|
-
onFocus: g,
|
|
25
|
-
slot: v,
|
|
26
|
-
...b
|
|
27
|
-
} = o, s = A({ suggest: n, existing: c, limit: m }), [N, p] = f.useState(!1), y = (i) => {
|
|
28
|
-
l(i), s.dismiss(i);
|
|
29
|
-
}, C = () => {
|
|
30
|
-
s.status !== "loading" && (s.status === "error" || s.items.length > 0 ? s.refresh() : s.ask());
|
|
31
|
-
};
|
|
32
|
-
return /* @__PURE__ */ r(
|
|
33
|
-
S.Provider,
|
|
34
|
-
{
|
|
35
|
-
value: {
|
|
36
|
-
candidates: s.items,
|
|
37
|
-
status: s.status,
|
|
38
|
-
error: s.error,
|
|
39
|
-
active: N,
|
|
40
|
-
ask: C,
|
|
41
|
-
pick: y,
|
|
42
|
-
dismiss: s.dismiss
|
|
43
|
-
},
|
|
44
|
-
children: /* @__PURE__ */ r(
|
|
45
|
-
d.div,
|
|
46
|
-
{
|
|
47
|
-
className: h("flex w-full min-w-0 flex-col gap-2", u),
|
|
48
|
-
onBlur: (i) => {
|
|
49
|
-
e == null || e(i), i.currentTarget.contains(i.relatedTarget) || p(!1);
|
|
50
|
-
},
|
|
51
|
-
onFocus: (i) => {
|
|
52
|
-
g == null || g(i), p(!0), a === "focus" && s.ask();
|
|
53
|
-
},
|
|
54
|
-
...b,
|
|
55
|
-
"data-slot": v ?? "suggest",
|
|
56
|
-
children: t
|
|
57
|
-
}
|
|
58
|
-
)
|
|
59
|
-
}
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
function X(o) {
|
|
63
|
-
const { label: n = "Suggest", offeringLabel: c = "Suggest different values", onClick: l, ...m } = o, a = k("SuggestMark"), t = a.candidates.length > 0;
|
|
64
|
-
return /* @__PURE__ */ r(
|
|
65
|
-
j,
|
|
66
|
-
{
|
|
67
|
-
busy: a.status === "loading" && !t,
|
|
68
|
-
label: t ? c : n,
|
|
69
|
-
offering: t,
|
|
70
|
-
onClick: (u) => {
|
|
71
|
-
l == null || l(u), u.defaultPrevented || a.ask();
|
|
72
|
-
},
|
|
73
|
-
...m
|
|
74
|
-
}
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
function q(o) {
|
|
78
|
-
const { className: n, empty: c, pending: l, slot: m, ...a } = o, t = k("SuggestList");
|
|
79
|
-
if (!t.active) return null;
|
|
80
|
-
const u = t.error !== null ? /* @__PURE__ */ r(d.p, { className: "text-destructive-foreground text-sm", "data-slot": "suggest-error", children: t.error }) : t.candidates.length > 0 ? t.candidates.map((e) => {
|
|
81
|
-
const g = e.label ?? e.value;
|
|
82
|
-
return (
|
|
83
|
-
// **Two buttons, so a group — not a ✕ inside the pill.** `Suggestion` *is* a
|
|
84
|
-
// `<button>`, and a button inside a button is not a thing the DOM has; the dismiss has
|
|
85
|
-
// to be a sibling. `ButtonGroup` is the house cluster: it collapses the inner radii and
|
|
86
|
-
// overlaps the shared border, so the pair reads as one pill with a divided end, and it
|
|
87
|
-
// requires the accessible name that a `role="group"` owes.
|
|
88
|
-
/* @__PURE__ */ x(L, { "aria-label": g, slot: "suggest-item", children: [
|
|
89
|
-
/* @__PURE__ */ r(M, { onSelect: t.pick, title: e.rationale, value: e.value, children: g }),
|
|
90
|
-
/* @__PURE__ */ r(
|
|
91
|
-
P,
|
|
92
|
-
{
|
|
93
|
-
"aria-label": `Dismiss ${g}`,
|
|
94
|
-
className: "h-auto min-h-[24px] self-stretch px-2 py-1",
|
|
95
|
-
onClick: () => t.dismiss(e.value),
|
|
96
|
-
pill: !0,
|
|
97
|
-
size: "sm",
|
|
98
|
-
slot: "suggest-dismiss",
|
|
99
|
-
variant: "outline",
|
|
100
|
-
children: /* @__PURE__ */ r(w, {})
|
|
101
|
-
}
|
|
102
|
-
)
|
|
103
|
-
] }, e.value)
|
|
104
|
-
);
|
|
105
|
-
}) : t.status === "loading" ? l ?? /* @__PURE__ */ x(
|
|
106
|
-
d.span,
|
|
107
|
-
{
|
|
108
|
-
className: "inline-flex items-center gap-2 text-muted-foreground text-sm",
|
|
109
|
-
"data-slot": "suggest-pending",
|
|
110
|
-
children: [
|
|
111
|
-
/* @__PURE__ */ r(R, { "aria-hidden": !0 }),
|
|
112
|
-
"Thinking…"
|
|
113
|
-
]
|
|
114
|
-
}
|
|
115
|
-
) : t.status === "ready" ? c ?? /* @__PURE__ */ r(d.span, { className: "text-muted-foreground text-sm", "data-slot": "suggest-empty", children: "Nothing to suggest." }) : null;
|
|
116
|
-
return u === null ? null : /* @__PURE__ */ r(T, { className: h("min-h-8", n), ...a, slot: m ?? "suggest-list", children: u });
|
|
117
|
-
}
|
|
118
|
-
export {
|
|
119
|
-
q as SuggestList,
|
|
120
|
-
X as SuggestMark,
|
|
121
|
-
I as SuggestRoot
|
|
122
|
-
};
|
|
123
|
-
//# sourceMappingURL=suggest.js.map
|
package/dist/suggest.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"suggest.js","sources":["../src/suggest.tsx"],"sourcesContent":["\"use client\";\n\nimport { XIcon } from \"lucide-react\";\nimport { ark } from \"@ark-ui/react/factory\";\nimport * as React from \"react\";\nimport { Button, ButtonGroup, cn, Spinner, Suggestion, Suggestions } from \"@kanzo-tech/ui\";\nimport { AiMark, type AiMarkProps } from \"./ai-mark.js\";\n// No alias any more, and that is the whole of this rename: the type and `@kanzo-tech/ui`'s\n// `Suggestion` component were both in scope in this file, both on public barrels, and one of them\n// had to be renamed at the import to be usable at all.\nimport type { Candidate } from \"./types.js\";\nimport { type AiStatus, useSuggestions } from \"./use-ai.js\";\n\ninterface SuggestCtx {\n candidates: Candidate[];\n status: AiStatus;\n error: string | null;\n /** The field, or something inside it, holds focus. */\n active: boolean;\n ask: () => void;\n pick: (value: string) => void;\n dismiss: (value: string) => void;\n}\n\nconst Ctx = React.createContext<SuggestCtx | null>(null);\nconst useCtx = (part: string): SuggestCtx => {\n const context = React.useContext(Ctx);\n if (!context) throw new Error(`${part} must render inside <SuggestRoot>`);\n return context;\n};\n\n/** When the source is asked. */\nexport type SuggestTrigger =\n /** On a press of the ✨. Nothing is requested until somebody asks for it. */\n | \"press\"\n /** As soon as the field takes focus. Prefetches, and bills a model for a tab-through. */\n | \"focus\";\n\nexport interface SuggestRootProps\n extends Omit<React.ComponentProps<typeof ark.div>, \"onSelect\"> {\n /** The source. Yields candidates; honour the `AbortSignal`. */\n suggest: (signal?: AbortSignal) => AsyncIterable<Candidate>;\n /** What the field already holds — these are never offered back. */\n existing?: string[];\n /** Where a chosen value goes. */\n onPick: (value: string) => void;\n /**\n * How many to take from the stream.\n *\n * @default 6\n */\n limit?: number;\n /**\n * @default \"press\"\n */\n trigger?: SuggestTrigger;\n}\n\n/**\n * Candidate values for one field, offered **in the flow underneath it**.\n *\n * This replaced a popover, and the popover is the whole story. Hiding the candidates behind a door\n * bought nothing and cost six workarounds, every one of them downstream of the door and not of the\n * candidates: a non-modal popover, because a modal one put `pointer-events: none` on the very field\n * the values were for; a `data-autofocus`, because a popover has to hand focus somewhere; a\n * `Delete`/`Backspace` handler, because a button inside `role=\"option\"` is unreachable by Tab; a\n * shared label id, because a popover names itself from a title and a listbox from a label; a ✕ that\n * had to be quieted because an offer looked identical to a committed tag; and a three-row window\n * with a refill loop in the hook to keep it full. None of them exist here.\n *\n * **Focus decides visibility, a press decides billing**, and they are deliberately not the same\n * gesture. The strip appears while the field has focus and there is something to show — which is\n * what keeps a form of eleven fields from becoming a wall, since only one field is focused at a\n * time. Asking is `trigger`, and it defaults to a press: a source that fires on focus bills a model\n * for a tab-through, and that is a decision a caller should make on purpose.\n */\nexport function SuggestRoot(props: SuggestRootProps) {\n const {\n suggest,\n existing,\n onPick,\n limit,\n trigger = \"press\",\n children,\n className,\n onBlur,\n onFocus,\n slot,\n ...rest\n } = props;\n\n const controller = useSuggestions({ suggest, existing, limit });\n const [active, setActive] = React.useState(false);\n\n const pick = (value: string) => {\n onPick(value);\n // A taken candidate is no longer a candidate. Nothing refills — the strip simply gets shorter,\n // which is the honest picture of a source that has already answered.\n controller.dismiss(value);\n };\n\n /**\n * **One meaning for a press, in both compounds: give me the assistance.**\n *\n * `Complete` reads that as *take the continuation* when there is one, because a ghost is a single\n * offer that needs a gesture to accept. A strip is N offers that already carry their own buttons,\n * so here the next useful thing is a different set — and after a failure it is a retry. The hook's\n * `ask` stays strict (idle only, so a press cannot re-bill a source that already answered) and\n * this is where the policy lives, which is why it is in the compound and not in `AiMark`.\n *\n * Loading is the one state where nothing happens: the mark is a spinner then, and a spinner you\n * can press is a lie.\n */\n const press = () => {\n if (controller.status === \"loading\") return;\n if (controller.status === \"error\" || controller.items.length > 0) controller.refresh();\n else controller.ask();\n };\n\n return (\n <Ctx.Provider\n value={{\n candidates: controller.items,\n status: controller.status,\n error: controller.error,\n active,\n ask: press,\n pick,\n dismiss: controller.dismiss,\n }}\n >\n <ark.div\n className={cn(\"flex w-full min-w-0 flex-col gap-2\", className)}\n onBlur={(event) => {\n onBlur?.(event);\n // `relatedTarget` is where focus is going. Inside our own subtree it is a pill being\n // clicked or the ✨ being tabbed to, and the strip must survive both.\n if (!event.currentTarget.contains(event.relatedTarget)) setActive(false);\n }}\n onFocus={(event) => {\n onFocus?.(event);\n setActive(true);\n if (trigger === \"focus\") controller.ask();\n }}\n {...rest}\n data-slot={slot ?? \"suggest\"}\n >\n {children}\n </ark.div>\n </Ctx.Provider>\n );\n}\n\n/**\n * The ✨ — **a mark, not a door.**\n *\n * At rest it says the field is model-assisted, which a reader can see without touching anything.\n * Pressing it asks. It no longer opens or closes a layer, so there is no `data-state` to light it\n * from and nothing to trap focus in.\n */\nexport function SuggestMark(\n props: Omit<AiMarkProps, \"busy\" | \"offering\"> & {\n /**\n * The accessible name while candidates are on offer, when `label` names the mark at rest.\n *\n * @default \"Suggest different values\"\n */\n offeringLabel?: string;\n }\n) {\n const { label = \"Suggest\", offeringLabel = \"Suggest different values\", onClick, ...rest } = props;\n const ctx = useCtx(\"SuggestMark\");\n // Bound exactly as `CompleteMark` binds it. A mark that paints one way over a ghost and another\n // over a strip reads as two products; `ai-mark.test.tsx` is the guard on that.\n const offering = ctx.candidates.length > 0;\n\n return (\n <AiMark\n busy={ctx.status === \"loading\" && !offering}\n label={offering ? offeringLabel : label}\n offering={offering}\n onClick={(event) => {\n onClick?.(event);\n if (!event.defaultPrevented) ctx.ask();\n }}\n {...rest}\n />\n );\n}\n\nexport interface SuggestListProps extends React.ComponentProps<typeof Suggestions> {\n /** Shown while the source is answering and has produced nothing yet. */\n pending?: React.ReactNode;\n /** Shown when the source finished with nothing to offer. */\n empty?: React.ReactNode;\n}\n\n/**\n * The strip, and the only part that decides whether anything is on screen.\n *\n * It renders `Suggestions` from `@kanzo-tech/ui` — a row of buttons that knows nothing about a\n * model — so everything model-shaped stays on this side of the boundary: the ✨, the engine, and\n * the rationale that comes back with a candidate.\n *\n * The rationale is the highlighted pill's `title` rather than a line under the strip. The line was\n * tried and it moved the layout on every hover; a `title` is the browser's own tooltip, reaches the\n * keyboard through focus, and costs no box.\n */\nexport function SuggestList(props: SuggestListProps) {\n const { className, empty, pending, slot, ...rest } = props;\n const ctx = useCtx(\"SuggestList\");\n\n if (!ctx.active) return null;\n\n const body =\n ctx.error !== null ? (\n <ark.p className=\"text-destructive-foreground text-sm\" data-slot=\"suggest-error\">\n {ctx.error}\n </ark.p>\n ) : ctx.candidates.length > 0 ? (\n ctx.candidates.map((candidate) => {\n const label = candidate.label ?? candidate.value;\n return (\n // **Two buttons, so a group — not a ✕ inside the pill.** `Suggestion` *is* a\n // `<button>`, and a button inside a button is not a thing the DOM has; the dismiss has\n // to be a sibling. `ButtonGroup` is the house cluster: it collapses the inner radii and\n // overlaps the shared border, so the pair reads as one pill with a divided end, and it\n // requires the accessible name that a `role=\"group\"` owes.\n <ButtonGroup aria-label={label} key={candidate.value} slot=\"suggest-item\">\n <Suggestion onSelect={ctx.pick} title={candidate.rationale} value={candidate.value}>\n {label}\n </Suggestion>\n {/* Always drawn, never on hover: a hover-only control is unreachable by a keyboard and\n absent on a touch pointer, and this strip is explicitly reachable by Tab. */}\n <Button\n aria-label={`Dismiss ${label}`}\n className=\"h-auto min-h-[24px] self-stretch px-2 py-1\"\n onClick={() => ctx.dismiss(candidate.value)}\n pill\n size=\"sm\"\n slot=\"suggest-dismiss\"\n variant=\"outline\"\n >\n <XIcon />\n </Button>\n </ButtonGroup>\n );\n })\n ) : ctx.status === \"loading\" ? (\n (pending ?? (\n <ark.span\n className=\"inline-flex items-center gap-2 text-muted-foreground text-sm\"\n data-slot=\"suggest-pending\"\n >\n <Spinner aria-hidden />\n Thinking…\n </ark.span>\n ))\n ) : ctx.status === \"ready\" ? (\n (empty ?? (\n <ark.span className=\"text-muted-foreground text-sm\" data-slot=\"suggest-empty\">\n Nothing to suggest.\n </ark.span>\n ))\n ) : null;\n\n if (body === null) return null;\n\n return (\n <Suggestions className={cn(\"min-h-8\", className)} {...rest} slot={slot ?? \"suggest-list\"}>\n {body}\n </Suggestions>\n );\n}\n"],"names":[],"mappings":";;;;;;;;AAwBA;AAEE;AACA;AACA;AACF;AA+CO;AACL;AAAM;AACJ;AACA;AACA;AACA;AACU;AACV;AACA;AACA;AACA;AACA;AACG;AAOH;AAGwB;AAgBxB;AAEgB;AAGlB;AACE;AAAK;AAAJ;AACQ;AACkB;AACJ;AACD;AAClB;AACK;AACL;AACoB;AAAA;AAGtB;AAAK;AAAJ;AAC8D;AAE3D;AAGuE;AACzE;AAEE;AAEoC;AACtC;AACI;AACe;AAElB;AAAA;AAAA;AACH;AAGN;AASO;AAUL;AAMA;AACE;AAAC;AAAA;AACoC;AACD;AAClC;AAEE;AACiC;AACnC;AACI;AAAA;AAGV;AAoBO;AACL;AAGA;AAEA;AAOM;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOI;AAEA;AAGA;AAAC;AAAA;AAC6B;AAClB;AACgC;AACtC;AACC;AACA;AACG;AAED;AAAA;AAAA;AAEX;AAAA;AAKF;AAAK;AAAJ;AACW;AACA;AAEV;AAAqB;AAAE;AAAA;AAAA;AAY/B;AAOF;;;;;;"}
|
package/dist/task.d.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { ark } from '@ark-ui/react/factory';
|
|
2
|
-
import * as React from "react";
|
|
3
|
-
/**
|
|
4
|
-
* The life of one step. Shared with `Tool`, which imports it — a tool call is a step with a name,
|
|
5
|
-
* and giving the two their own four-word vocabularies is how they end up disagreeing.
|
|
6
|
-
*/
|
|
7
|
-
export type RunState = "pending" | "running" | "done" | "failed";
|
|
8
|
-
/** The word for each state, in one place because `Tool`'s badge says the same four. */
|
|
9
|
-
export declare const RUN_LABEL: Record<RunState, string>;
|
|
10
|
-
/**
|
|
11
|
-
* The mark for each state — **shared with `Tool`, which imports it.** One icon family at one
|
|
12
|
-
* stroke weight, tinted by the family's own `-foreground` token, which for a status family is the
|
|
13
|
-
* readable-on-the-page variant rather than ink on a fill (`tokens.css` states both meanings).
|
|
14
|
-
*
|
|
15
|
-
* This was four `Status` dots, and `Status` is a presence dot: its recipe carries `ring-2
|
|
16
|
-
* ring-background`, a halo for sitting on an avatar, which cut a 2px hole in the rail the marks
|
|
17
|
-
* stand on. Fighting it took a `size-4` override of `size="lg"`'s 12px and a `bg-transparent
|
|
18
|
-
* ring-1` hollowing for pending — a primitive argued with in three places is the wrong primitive.
|
|
19
|
-
* `Tool` already drew these four states as icons; now there is one drawing of them, not two.
|
|
20
|
-
*
|
|
21
|
-
* Unsized on purpose: the parent sizes the glyph, so the same table serves a 16px rail mark and a
|
|
22
|
-
* 12px badge icon.
|
|
23
|
-
*/
|
|
24
|
-
export declare const RUN_MARK: Record<RunState, React.ReactNode>;
|
|
25
|
-
export declare const TaskList: (props: React.ComponentProps<typeof ark.ol>) => React.JSX.Element;
|
|
26
|
-
export interface TaskProps extends React.ComponentProps<typeof ark.li> {
|
|
27
|
-
state?: RunState;
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* One labelled step. The state is declared here and read off `data-state` by the parts.
|
|
31
|
-
*
|
|
32
|
-
* The rail through the markers is the step's own `::after` rather than a border on the list: a run
|
|
33
|
-
* of five has to read as one run, and the last step must not trail a line into nothing.
|
|
34
|
-
*/
|
|
35
|
-
export declare const Task: (props: TaskProps) => React.JSX.Element;
|
|
36
|
-
export declare const TaskTitle: (props: React.ComponentProps<typeof ark.span>) => React.JSX.Element;
|
|
37
|
-
/**
|
|
38
|
-
* The step's marker — {@link RUN_MARK} for this step's state, in a box the rail runs out of.
|
|
39
|
-
*
|
|
40
|
-
* The mark is decorative to a screen reader; the word beside it is what carries the state, and it
|
|
41
|
-
* is written here as `sr-only` rather than assumed of the title. Pass children to say it in your
|
|
42
|
-
* own words.
|
|
43
|
-
*
|
|
44
|
-
* The box is `size-5` and centred so the mark lands on the first line's optical centre: `text-sm`
|
|
45
|
-
* is a 20px line box, and a marker aligned to the top of a wrapping title sits above its own text.
|
|
46
|
-
*/
|
|
47
|
-
export declare const TaskStatus: (props: React.ComponentProps<typeof ark.span>) => React.JSX.Element;
|
|
48
|
-
//# sourceMappingURL=task.d.ts.map
|
package/dist/task.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"task.d.ts","sourceRoot":"","sources":["../src/task.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,GAAG,EAAE,MAAM,uBAAuB,CAAC;AAE5C,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEjE,uFAAuF;AACvF,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAK9C,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAKtD,CAAC;AAkBF,eAAO,MAAM,QAAQ,GAAI,OAAO,KAAK,CAAC,cAAc,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC,sBAalE,CAAC;AAEF,MAAM,WAAW,SAAU,SAAQ,KAAK,CAAC,cAAc,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC;IACpE,KAAK,CAAC,EAAE,QAAQ,CAAC;CAClB;AAED;;;;;GAKG;AACH,eAAO,MAAM,IAAI,GAAI,OAAO,SAAS,sBAoBpC,CAAC;AAEF,eAAO,MAAM,SAAS,GAAI,OAAO,KAAK,CAAC,cAAc,CAAC,OAAO,GAAG,CAAC,IAAI,CAAC,sBAUrE,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,UAAU,GAAI,OAAO,KAAK,CAAC,cAAc,CAAC,OAAO,GAAG,CAAC,IAAI,CAAC,sBAmBtE,CAAC"}
|
package/dist/task.js
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { jsx as s, jsxs as u } from "react/jsx-runtime";
|
|
3
|
-
import { ark as o } from "@ark-ui/react/factory";
|
|
4
|
-
import { CircleXIcon as f, CircleCheckIcon as m, CircleDashedIcon as g } from "lucide-react";
|
|
5
|
-
import * as c from "react";
|
|
6
|
-
import { cn as l, Spinner as p } from "@kanzo-tech/ui";
|
|
7
|
-
const x = {
|
|
8
|
-
pending: "Pending",
|
|
9
|
-
running: "Running",
|
|
10
|
-
done: "Done",
|
|
11
|
-
failed: "Failed"
|
|
12
|
-
}, k = {
|
|
13
|
-
pending: /* @__PURE__ */ s(g, { className: "text-muted-foreground" }),
|
|
14
|
-
running: /* @__PURE__ */ s(p, { "aria-hidden": !0, className: "text-info-foreground" }),
|
|
15
|
-
done: /* @__PURE__ */ s(m, { className: "text-success-foreground" }),
|
|
16
|
-
failed: /* @__PURE__ */ s(f, { className: "text-destructive-foreground" })
|
|
17
|
-
}, N = [
|
|
18
|
-
"min-w-0 flex-1 truncate leading-5",
|
|
19
|
-
"group-data-[state=pending]/task:text-muted-foreground",
|
|
20
|
-
"group-data-[state=failed]/task:text-destructive-foreground"
|
|
21
|
-
].join(" "), d = c.createContext(null), h = (e) => {
|
|
22
|
-
const t = c.useContext(d);
|
|
23
|
-
if (!t) throw new Error(`${e} must render inside <Task>`);
|
|
24
|
-
return t;
|
|
25
|
-
}, R = (e) => {
|
|
26
|
-
const { className: t, slot: a, ...r } = e;
|
|
27
|
-
return /* @__PURE__ */ s(
|
|
28
|
-
o.ol,
|
|
29
|
-
{
|
|
30
|
-
className: l("flex w-full list-none flex-col gap-2", t),
|
|
31
|
-
role: "list",
|
|
32
|
-
...r,
|
|
33
|
-
"data-slot": a ?? "task-list"
|
|
34
|
-
}
|
|
35
|
-
);
|
|
36
|
-
}, b = (e) => {
|
|
37
|
-
const { state: t = "pending", className: a, children: r, slot: i, ...n } = e;
|
|
38
|
-
return /* @__PURE__ */ s(d.Provider, { value: { state: t }, children: /* @__PURE__ */ s(
|
|
39
|
-
o.li,
|
|
40
|
-
{
|
|
41
|
-
className: l(
|
|
42
|
-
"group/task relative flex w-full items-start gap-2.5 text-sm",
|
|
43
|
-
"after:absolute after:top-5 after:-bottom-2 after:start-2.5 after:w-px after:bg-border",
|
|
44
|
-
"last:after:hidden",
|
|
45
|
-
a
|
|
46
|
-
),
|
|
47
|
-
"data-state": t,
|
|
48
|
-
...n,
|
|
49
|
-
"data-slot": i ?? "task",
|
|
50
|
-
children: r
|
|
51
|
-
}
|
|
52
|
-
) });
|
|
53
|
-
}, j = (e) => {
|
|
54
|
-
const { className: t, slot: a, ...r } = e;
|
|
55
|
-
return /* @__PURE__ */ s(
|
|
56
|
-
o.span,
|
|
57
|
-
{
|
|
58
|
-
className: l(N, t),
|
|
59
|
-
...r,
|
|
60
|
-
"data-slot": a ?? "task-title"
|
|
61
|
-
}
|
|
62
|
-
);
|
|
63
|
-
}, z = (e) => {
|
|
64
|
-
const { className: t, children: a, slot: r, ...i } = e, { state: n } = h("TaskStatus");
|
|
65
|
-
return /* @__PURE__ */ u(
|
|
66
|
-
o.span,
|
|
67
|
-
{
|
|
68
|
-
className: l(
|
|
69
|
-
"relative z-10 flex size-5 shrink-0 items-center justify-center",
|
|
70
|
-
"[&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0",
|
|
71
|
-
t
|
|
72
|
-
),
|
|
73
|
-
"data-state": n,
|
|
74
|
-
...i,
|
|
75
|
-
"data-slot": r ?? "task-status",
|
|
76
|
-
children: [
|
|
77
|
-
k[n],
|
|
78
|
-
/* @__PURE__ */ s(o.span, { className: "sr-only", children: a ?? x[n] })
|
|
79
|
-
]
|
|
80
|
-
}
|
|
81
|
-
);
|
|
82
|
-
};
|
|
83
|
-
export {
|
|
84
|
-
x as RUN_LABEL,
|
|
85
|
-
k as RUN_MARK,
|
|
86
|
-
b as Task,
|
|
87
|
-
R as TaskList,
|
|
88
|
-
z as TaskStatus,
|
|
89
|
-
j as TaskTitle
|
|
90
|
-
};
|
|
91
|
-
//# sourceMappingURL=task.js.map
|
package/dist/task.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"task.js","sources":["../src/task.tsx"],"sourcesContent":["\"use client\";\n\nimport { ark } from \"@ark-ui/react/factory\";\nimport { CircleCheckIcon, CircleDashedIcon, CircleXIcon } from \"lucide-react\";\nimport * as React from \"react\";\nimport { cn, Spinner } from \"@kanzo-tech/ui\";\n\n/**\n * The life of one step. Shared with `Tool`, which imports it — a tool call is a step with a name,\n * and giving the two their own four-word vocabularies is how they end up disagreeing.\n */\nexport type RunState = \"pending\" | \"running\" | \"done\" | \"failed\";\n\n/** The word for each state, in one place because `Tool`'s badge says the same four. */\nexport const RUN_LABEL: Record<RunState, string> = {\n pending: \"Pending\",\n running: \"Running\",\n done: \"Done\",\n failed: \"Failed\",\n};\n\n/**\n * The mark for each state — **shared with `Tool`, which imports it.** One icon family at one\n * stroke weight, tinted by the family's own `-foreground` token, which for a status family is the\n * readable-on-the-page variant rather than ink on a fill (`tokens.css` states both meanings).\n *\n * This was four `Status` dots, and `Status` is a presence dot: its recipe carries `ring-2\n * ring-background`, a halo for sitting on an avatar, which cut a 2px hole in the rail the marks\n * stand on. Fighting it took a `size-4` override of `size=\"lg\"`'s 12px and a `bg-transparent\n * ring-1` hollowing for pending — a primitive argued with in three places is the wrong primitive.\n * `Tool` already drew these four states as icons; now there is one drawing of them, not two.\n *\n * Unsized on purpose: the parent sizes the glyph, so the same table serves a 16px rail mark and a\n * 12px badge icon.\n */\nexport const RUN_MARK: Record<RunState, React.ReactNode> = {\n pending: <CircleDashedIcon className=\"text-muted-foreground\" />,\n running: <Spinner aria-hidden className=\"text-info-foreground\" />,\n done: <CircleCheckIcon className=\"text-success-foreground\" />,\n failed: <CircleXIcon className=\"text-destructive-foreground\" />,\n};\n\n/** The state reaches the title through the root's `data-state`, so the title needs no context. */\n// One weight across the five rows. The mark already says which step is running and which failed,\n// and a second emphasis on the same fact reads as three type styles in a five-row list.\nconst TITLE = [\n \"min-w-0 flex-1 truncate leading-5\",\n \"group-data-[state=pending]/task:text-muted-foreground\",\n \"group-data-[state=failed]/task:text-destructive-foreground\",\n].join(\" \");\n\nconst Ctx = React.createContext<{ state: RunState } | null>(null);\nconst useCtx = (part: string) => {\n const c = React.useContext(Ctx);\n if (!c) throw new Error(`${part} must render inside <Task>`);\n return c;\n};\n\nexport const TaskList = (props: React.ComponentProps<typeof ark.ol>) => {\n const { className, slot, ...rest } = props;\n\n return (\n <ark.ol\n className={cn(\"flex w-full list-none flex-col gap-2\", className)}\n // `role=\"list\"`, because `list-none` takes the implicit one away in WebKit. See\n // `DiagnosticList` in `@kanzo-tech/ui` for the argument.\n role=\"list\"\n {...rest}\n data-slot={slot ?? \"task-list\"}\n />\n );\n};\n\nexport interface TaskProps extends React.ComponentProps<typeof ark.li> {\n state?: RunState;\n}\n\n/**\n * One labelled step. The state is declared here and read off `data-state` by the parts.\n *\n * The rail through the markers is the step's own `::after` rather than a border on the list: a run\n * of five has to read as one run, and the last step must not trail a line into nothing.\n */\nexport const Task = (props: TaskProps) => {\n const { state = \"pending\", className, children, slot, ...rest } = props;\n\n return (\n <Ctx.Provider value={{ state }}>\n <ark.li\n className={cn(\n \"group/task relative flex w-full items-start gap-2.5 text-sm\",\n \"after:absolute after:top-5 after:-bottom-2 after:start-2.5 after:w-px after:bg-border\",\n \"last:after:hidden\",\n className\n )}\n data-state={state}\n {...rest}\n data-slot={slot ?? \"task\"}\n >\n {children}\n </ark.li>\n </Ctx.Provider>\n );\n};\n\nexport const TaskTitle = (props: React.ComponentProps<typeof ark.span>) => {\n const { className, slot, ...rest } = props;\n\n return (\n <ark.span\n className={cn(TITLE, className)}\n {...rest}\n data-slot={slot ?? \"task-title\"}\n />\n );\n};\n\n/**\n * The step's marker — {@link RUN_MARK} for this step's state, in a box the rail runs out of.\n *\n * The mark is decorative to a screen reader; the word beside it is what carries the state, and it\n * is written here as `sr-only` rather than assumed of the title. Pass children to say it in your\n * own words.\n *\n * The box is `size-5` and centred so the mark lands on the first line's optical centre: `text-sm`\n * is a 20px line box, and a marker aligned to the top of a wrapping title sits above its own text.\n */\nexport const TaskStatus = (props: React.ComponentProps<typeof ark.span>) => {\n const { className, children, slot, ...rest } = props;\n const { state } = useCtx(\"TaskStatus\");\n\n return (\n <ark.span\n className={cn(\n \"relative z-10 flex size-5 shrink-0 items-center justify-center\",\n \"[&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0\",\n className\n )}\n data-state={state}\n {...rest}\n data-slot={slot ?? \"task-status\"}\n >\n {RUN_MARK[state]}\n <ark.span className=\"sr-only\">{children ?? RUN_LABEL[state]}</ark.span>\n </ark.span>\n );\n};\n"],"names":[],"mappings":";;;;;;AAcO;AAA4C;AACxC;AACA;AACH;AAER;AAgB2D;AACI;AACE;AACJ;AAE7D;AAKc;AACZ;AACA;AAEF;AAIE;AACA;AACA;AACF;AAGE;AAEA;AACE;AAAK;AAAJ;AACgE;AAG1D;AACD;AACe;AAAA;AAGzB;AAaE;AAEA;AAEI;AAAK;AAAJ;AACY;AACT;AACA;AACA;AACA;AAAA;AAEU;AACR;AACe;AAElB;AAAA;AAIT;AAGE;AAEA;AACE;AAAK;AAAJ;AAC+B;AAC1B;AACe;AAAA;AAGzB;AAaE;AAGA;AACE;AAAK;AAAJ;AACY;AACT;AACA;AACA;AAAA;AAEU;AACR;AACe;AAElB;AAAc;AAC6C;AAAA;AAAA;AAGlE;;;;;;;;;"}
|
package/dist/types.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/** A streamed value candidate produced by `useSuggestions`. */
|
|
2
|
-
export interface Candidate {
|
|
3
|
-
/** Primitive value committed when the row is picked. */
|
|
4
|
-
value: string;
|
|
5
|
-
/** Human label; defaults to `value`. */
|
|
6
|
-
label?: string;
|
|
7
|
-
/** Optional rationale shown under the label. */
|
|
8
|
-
rationale?: string;
|
|
9
|
-
}
|
|
10
|
-
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,MAAM,WAAW,SAAS;IACxB,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gDAAgD;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB"}
|