@intentface/ui 0.0.0 → 0.7.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/LICENSE +21 -0
- package/README.md +145 -2
- package/dist/ask-user/ask-user.d.ts +104 -0
- package/dist/ask-user/ask-user.js +245 -0
- package/dist/ask-user/index.d.ts +3 -0
- package/dist/ask-user/index.js +3 -0
- package/dist/ask-user/index.parts.d.ts +1 -0
- package/dist/ask-user/index.parts.js +5 -0
- package/dist/attachments/attachments.d.ts +73 -0
- package/dist/attachments/attachments.js +121 -0
- package/dist/attachments/index.d.ts +3 -0
- package/dist/attachments/index.js +3 -0
- package/dist/attachments/index.parts.d.ts +1 -0
- package/dist/attachments/index.parts.js +5 -0
- package/dist/chip/chip.d.ts +45 -0
- package/dist/chip/chip.js +43 -0
- package/dist/chip/index.d.ts +2 -0
- package/dist/chip/index.js +2 -0
- package/dist/chip/index.parts.d.ts +1 -0
- package/dist/chip/index.parts.js +5 -0
- package/dist/chip-markdown.d.ts +25 -0
- package/dist/chip-markdown.js +54 -0
- package/dist/composer/actions.d.ts +35 -0
- package/dist/composer/actions.js +76 -0
- package/dist/composer/ask-user-machine.d.ts +52 -0
- package/dist/composer/ask-user-machine.js +164 -0
- package/dist/composer/attachments-machine.d.ts +34 -0
- package/dist/composer/attachments-machine.js +91 -0
- package/dist/composer/attachments.d.ts +16 -0
- package/dist/composer/attachments.js +32 -0
- package/dist/composer/command-list.d.ts +49 -0
- package/dist/composer/command-list.js +360 -0
- package/dist/composer/commands.d.ts +28 -0
- package/dist/composer/commands.js +56 -0
- package/dist/composer/container.d.ts +3 -0
- package/dist/composer/container.js +32 -0
- package/dist/composer/controller.d.ts +3 -0
- package/dist/composer/controller.js +6 -0
- package/dist/composer/editor-dom.d.ts +57 -0
- package/dist/composer/editor-dom.js +517 -0
- package/dist/composer/fuzzy.d.ts +4 -0
- package/dist/composer/fuzzy.js +49 -0
- package/dist/composer/index.d.ts +25 -0
- package/dist/composer/index.js +13 -0
- package/dist/composer/index.parts.d.ts +11 -0
- package/dist/composer/index.parts.js +15 -0
- package/dist/composer/internals.d.ts +30 -0
- package/dist/composer/internals.js +102 -0
- package/dist/composer/keyboard.d.ts +61 -0
- package/dist/composer/keyboard.js +75 -0
- package/dist/composer/panel.d.ts +28 -0
- package/dist/composer/panel.js +76 -0
- package/dist/composer/placeholder.d.ts +10 -0
- package/dist/composer/placeholder.js +8 -0
- package/dist/composer/popover.d.ts +12 -0
- package/dist/composer/popover.js +62 -0
- package/dist/composer/prefix-detection.d.ts +23 -0
- package/dist/composer/prefix-detection.js +67 -0
- package/dist/composer/root.d.ts +21 -0
- package/dist/composer/root.js +124 -0
- package/dist/composer/segments.d.ts +108 -0
- package/dist/composer/segments.js +366 -0
- package/dist/composer/store.d.ts +98 -0
- package/dist/composer/store.js +457 -0
- package/dist/composer/textarea.d.ts +39 -0
- package/dist/composer/textarea.js +64 -0
- package/dist/composer/trigger-tracker.d.ts +12 -0
- package/dist/composer/trigger-tracker.js +90 -0
- package/dist/composer/types.d.ts +127 -0
- package/dist/composer/types.js +4 -0
- package/dist/composer/use-composer-editor.d.ts +43 -0
- package/dist/composer/use-composer-editor.js +653 -0
- package/dist/generative/catalog.d.ts +177 -0
- package/dist/generative/catalog.js +20 -0
- package/dist/generative/index.d.ts +26 -0
- package/dist/generative/index.js +20 -0
- package/dist/generative/json-schema.d.ts +45 -0
- package/dist/generative/json-schema.js +216 -0
- package/dist/generative/markdown.d.ts +21 -0
- package/dist/generative/markdown.js +76 -0
- package/dist/generative/tree.d.ts +56 -0
- package/dist/generative/tree.js +365 -0
- package/dist/generative/use-tree.d.ts +12 -0
- package/dist/generative/use-tree.js +64 -0
- package/dist/internal/change-event-details.d.ts +12 -0
- package/dist/internal/change-event-details.js +16 -0
- package/dist/internal/collapsible.d.ts +22 -0
- package/dist/internal/collapsible.js +94 -0
- package/dist/internal/collection.d.ts +32 -0
- package/dist/internal/collection.js +72 -0
- package/dist/internal/iso-layout-effect.d.ts +2 -0
- package/dist/internal/iso-layout-effect.js +6 -0
- package/dist/internal/portal.d.ts +6 -0
- package/dist/internal/portal.js +18 -0
- package/dist/internal/primitive-props.d.ts +4 -0
- package/dist/internal/primitive-props.js +3 -0
- package/dist/internal/render/baseTypes.d.ts +18 -0
- package/dist/internal/render/baseTypes.js +4 -0
- package/dist/internal/render/empty.d.ts +3 -0
- package/dist/internal/render/empty.js +5 -0
- package/dist/internal/render/getReactElementRef.d.ts +5 -0
- package/dist/internal/render/getReactElementRef.js +15 -0
- package/dist/internal/render/getStateAttributesProps.d.ts +4 -0
- package/dist/internal/render/getStateAttributesProps.js +23 -0
- package/dist/internal/render/mergeObjects.d.ts +1 -0
- package/dist/internal/render/mergeObjects.js +14 -0
- package/dist/internal/render/mergeProps.d.ts +55 -0
- package/dist/internal/render/mergeProps.js +183 -0
- package/dist/internal/render/positioning.d.ts +33 -0
- package/dist/internal/render/positioning.js +112 -0
- package/dist/internal/render/reactVersion.d.ts +3 -0
- package/dist/internal/render/reactVersion.js +7 -0
- package/dist/internal/render/resolveClassName.d.ts +8 -0
- package/dist/internal/render/resolveClassName.js +12 -0
- package/dist/internal/render/resolveStyle.d.ts +8 -0
- package/dist/internal/render/resolveStyle.js +12 -0
- package/dist/internal/render/transition.d.ts +47 -0
- package/dist/internal/render/transition.js +259 -0
- package/dist/internal/render/types.d.ts +74 -0
- package/dist/internal/render/types.js +3 -0
- package/dist/internal/render/useMergedRefs.d.ts +21 -0
- package/dist/internal/render/useMergedRefs.js +102 -0
- package/dist/internal/render/useRefWithInit.d.ts +10 -0
- package/dist/internal/render/useRefWithInit.js +12 -0
- package/dist/internal/render/useRender.d.ts +71 -0
- package/dist/internal/render/useRender.js +12 -0
- package/dist/internal/render/useRenderElement.d.ts +62 -0
- package/dist/internal/render/useRenderElement.js +157 -0
- package/dist/internal/render/warn.d.ts +1 -0
- package/dist/internal/render/warn.js +15 -0
- package/dist/internal/safe-polygon.d.ts +21 -0
- package/dist/internal/safe-polygon.js +179 -0
- package/dist/internal/scroll-into-view-within.d.ts +1 -0
- package/dist/internal/scroll-into-view-within.js +26 -0
- package/dist/internal/standard-schema.d.ts +38 -0
- package/dist/internal/standard-schema.js +4 -0
- package/dist/internal/state-mappings.d.ts +8 -0
- package/dist/internal/state-mappings.js +17 -0
- package/dist/internal/use-controlled.d.ts +19 -0
- package/dist/internal/use-controlled.js +33 -0
- package/dist/message/index.d.ts +3 -0
- package/dist/message/index.js +3 -0
- package/dist/message/index.parts.d.ts +1 -0
- package/dist/message/index.parts.js +5 -0
- package/dist/message/message.d.ts +58 -0
- package/dist/message/message.js +93 -0
- package/dist/message-utils.d.ts +55 -0
- package/dist/message-utils.js +79 -0
- package/dist/nav/index.d.ts +4 -0
- package/dist/nav/index.js +5 -0
- package/dist/nav/index.parts.d.ts +3 -0
- package/dist/nav/index.parts.js +7 -0
- package/dist/nav/nav.d.ts +152 -0
- package/dist/nav/nav.js +660 -0
- package/dist/nav/store.d.ts +50 -0
- package/dist/nav/store.js +95 -0
- package/dist/reasoning/index.d.ts +3 -0
- package/dist/reasoning/index.js +3 -0
- package/dist/reasoning/index.parts.d.ts +1 -0
- package/dist/reasoning/index.parts.js +5 -0
- package/dist/reasoning/reasoning.d.ts +38 -0
- package/dist/reasoning/reasoning.js +67 -0
- package/dist/shell/index.d.ts +5 -0
- package/dist/shell/index.js +5 -0
- package/dist/shell/index.parts.d.ts +3 -0
- package/dist/shell/index.parts.js +7 -0
- package/dist/shell/shell.d.ts +108 -0
- package/dist/shell/shell.js +390 -0
- package/dist/shell/store.d.ts +112 -0
- package/dist/shell/store.js +134 -0
- package/dist/steps/index.d.ts +2 -0
- package/dist/steps/index.js +2 -0
- package/dist/steps/index.parts.d.ts +1 -0
- package/dist/steps/index.parts.js +5 -0
- package/dist/steps/steps.d.ts +90 -0
- package/dist/steps/steps.js +116 -0
- package/dist/tabs/anchored.d.ts +59 -0
- package/dist/tabs/anchored.js +109 -0
- package/dist/tabs/hover.d.ts +33 -0
- package/dist/tabs/hover.js +169 -0
- package/dist/tabs/index.d.ts +5 -0
- package/dist/tabs/index.js +4 -0
- package/dist/tabs/index.parts.d.ts +4 -0
- package/dist/tabs/index.parts.js +8 -0
- package/dist/tabs/store.d.ts +131 -0
- package/dist/tabs/store.js +248 -0
- package/dist/tabs/tabs.d.ts +217 -0
- package/dist/tabs/tabs.js +458 -0
- package/dist/thread/geometry.d.ts +23 -0
- package/dist/thread/geometry.js +48 -0
- package/dist/thread/index.d.ts +4 -0
- package/dist/thread/index.js +3 -0
- package/dist/thread/index.parts.d.ts +1 -0
- package/dist/thread/index.parts.js +5 -0
- package/dist/thread/stores.d.ts +21 -0
- package/dist/thread/stores.js +63 -0
- package/dist/thread/thread.d.ts +57 -0
- package/dist/thread/thread.js +663 -0
- package/dist/types.d.ts +58 -0
- package/dist/types.js +17 -0
- package/package.json +120 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Intentface
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,5 +1,148 @@
|
|
|
1
1
|
# @intentface/ui
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Headless UI primitives for React — the behavior, state, and wire formats for AI
|
|
4
|
+
chat interfaces and the app shells around them, with no styling of their own.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
**The package owns behavior; you own every class.** There is no styled companion
|
|
7
|
+
package and no theme to opt out of.
|
|
8
|
+
The demos on each [docs](https://ui.intentface.com) page are self-contained
|
|
9
|
+
Tailwind, meant to be copied and restyled.
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
"use client";
|
|
13
|
+
import { Composer } from "@intentface/ui/composer";
|
|
14
|
+
import { Message } from "@intentface/ui/message";
|
|
15
|
+
import { Thread } from "@intentface/ui/thread";
|
|
16
|
+
|
|
17
|
+
export const Chat = ({ messages, send }: ChatProps) => (
|
|
18
|
+
<Thread.Root autoScroll="follow" className="relative flex h-dvh flex-col">
|
|
19
|
+
<Thread.Viewport className="flex-1 overflow-y-auto">
|
|
20
|
+
<Thread.Content className="mx-auto flex max-w-2xl flex-col gap-4 p-4">
|
|
21
|
+
{messages.map((message) => (
|
|
22
|
+
<Message.Root key={message.id} role={message.role}>
|
|
23
|
+
<Message.Text>{message.text}</Message.Text>
|
|
24
|
+
</Message.Root>
|
|
25
|
+
))}
|
|
26
|
+
</Thread.Content>
|
|
27
|
+
</Thread.Viewport>
|
|
28
|
+
|
|
29
|
+
<Thread.Composer className="p-4">
|
|
30
|
+
<Composer.Root onSubmit={send}>
|
|
31
|
+
<Composer.Container className="rounded-xl border p-2">
|
|
32
|
+
<Composer.Textarea placeholder="Ask anything" />
|
|
33
|
+
<Composer.Actions>
|
|
34
|
+
<Composer.Submit>Send</Composer.Submit>
|
|
35
|
+
</Composer.Actions>
|
|
36
|
+
</Composer.Container>
|
|
37
|
+
</Composer.Root>
|
|
38
|
+
</Thread.Composer>
|
|
39
|
+
</Thread.Root>
|
|
40
|
+
);
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Every class above is yours; delete them and the chat still works, it just looks
|
|
44
|
+
like unstyled HTML.
|
|
45
|
+
|
|
46
|
+
## Status
|
|
47
|
+
|
|
48
|
+
Pre-1.0. The primitives are in real use, but the API is still settling and
|
|
49
|
+
minor releases may contain breaking changes — the usual `0.x` semver contract.
|
|
50
|
+
Pin an exact version if that matters to you, and check the
|
|
51
|
+
[changelog](https://github.com/Intentface/intentface-ui/blob/main/packages/ui/CHANGELOG.md)
|
|
52
|
+
when upgrading.
|
|
53
|
+
|
|
54
|
+
## Install
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
bun add @intentface/ui
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`react` and `react-dom` (v19+) are peer dependencies. Nothing else ships with
|
|
61
|
+
it beyond one small runtime dep (`@floating-ui/dom` for anchored positioning) —
|
|
62
|
+
no editor framework, no animation library.
|
|
63
|
+
|
|
64
|
+
## What's inside
|
|
65
|
+
|
|
66
|
+
Each primitive is a separate entry point, exporting an unstyled compound
|
|
67
|
+
component that renders semantic DOM with `data-*` state attributes, context
|
|
68
|
+
hooks, and a `render` prop for swapping the underlying element. Shell, nav and
|
|
69
|
+
tabs are app-shell parts rather than chat parts, and are useful on their own:
|
|
70
|
+
|
|
71
|
+
| Import | What it is |
|
|
72
|
+
| --- | --- |
|
|
73
|
+
| `@intentface/ui/composer` | Rich-text input over a purpose-built contenteditable engine: `/` and `@` command palette, inline chips, attachments, ask-user flow. One store per `<Composer.Root>`, or bring your own via `Composer.createStore()`. |
|
|
74
|
+
| `@intentface/ui/thread` | Scroll container with at-bottom detection, auto-follow, and docked-composer measurement. |
|
|
75
|
+
| `@intentface/ui/message` | Message parts, turns, chip-segmented text, selection hooks. |
|
|
76
|
+
| `@intentface/ui/steps`, `/reasoning` | Tool-call timelines and reasoning disclosure. |
|
|
77
|
+
| `@intentface/ui/shell` | A collapsible, resizable sidebar beside the viewport it shares the screen with, with an edge hotspot. |
|
|
78
|
+
| `@intentface/ui/nav` | A nav tree with roving focus, typeahead, collapsible groups, and a guide ladder down the left edge. |
|
|
79
|
+
| `@intentface/ui/tabs` | An open-ended, closable tab collection whose panel renders in the layout or anchored to its own tab. |
|
|
80
|
+
| `@intentface/ui/chip`, `/attachments`, `/ask-user` | The remaining building blocks. |
|
|
81
|
+
| `@intentface/ui/generative` | Experimental. Typed helpers that check a model's UI tool call against a catalog, read it while it streams, resolve its data bindings, and write it as Markdown. |
|
|
82
|
+
| `@intentface/ui/types` | The structural message contract + part type guards. |
|
|
83
|
+
| `@intentface/ui/message-utils` | Part segmentation, turn grouping, reasoning/source derivation. |
|
|
84
|
+
| `@intentface/ui/chip-markdown` | The self-describing chip wire format. |
|
|
85
|
+
|
|
86
|
+
## AI SDK, structurally
|
|
87
|
+
|
|
88
|
+
The types are framework-agnostic. A Vercel AI SDK `UIMessage` satisfies
|
|
89
|
+
`ChatMessage` structurally, so there's no adapter — pass SDK messages straight
|
|
90
|
+
into the utilities and components:
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
import { groupTurns } from "@intentface/ui/message-utils";
|
|
94
|
+
import type { UIMessage } from "ai";
|
|
95
|
+
|
|
96
|
+
const turns = groupTurns(messages); // messages: UIMessage[]
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## React Server Components
|
|
100
|
+
|
|
101
|
+
Each primitive is a namespace, and the root is explicit — `Message.Root`, not
|
|
102
|
+
`Message`. Server components can reach every part:
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
// A server component — no "use client" needed.
|
|
106
|
+
import { Message } from "@intentface/ui/message";
|
|
107
|
+
|
|
108
|
+
export const Transcript = ({ messages }: { messages: ChatMessage[] }) => (
|
|
109
|
+
<>
|
|
110
|
+
{messages.map((message) => (
|
|
111
|
+
<Message.Root key={message.id} role={message.role}>
|
|
112
|
+
<Message.Text>{text(message)}</Message.Text>
|
|
113
|
+
</Message.Root>
|
|
114
|
+
))}
|
|
115
|
+
</>
|
|
116
|
+
);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The parts are still client components — they carry their own `"use client"` — so
|
|
120
|
+
interactive props behave the way React requires. Pass an event handler from a
|
|
121
|
+
server component and you'll get React's usual error; move that piece into a
|
|
122
|
+
client component:
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
"use client";
|
|
126
|
+
import { Composer } from "@intentface/ui/composer";
|
|
127
|
+
|
|
128
|
+
export const Chat = () => (
|
|
129
|
+
<Composer.Root onSubmit={(data) => send(data)}>
|
|
130
|
+
<Composer.Container>
|
|
131
|
+
<Composer.Textarea />
|
|
132
|
+
</Composer.Container>
|
|
133
|
+
</Composer.Root>
|
|
134
|
+
);
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Hooks (`useComposer`, `useThread`, `useReasoning`, …) are client-only, as usual.
|
|
138
|
+
The pure modules — `/types`, `/message-utils`, `/chip-markdown` — carry no
|
|
139
|
+
directive and import fine anywhere.
|
|
140
|
+
|
|
141
|
+
## Docs
|
|
142
|
+
|
|
143
|
+
Full guides, live demos, and the API reference at
|
|
144
|
+
[ui.intentface.com](https://ui.intentface.com).
|
|
145
|
+
|
|
146
|
+
## License
|
|
147
|
+
|
|
148
|
+
MIT
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { type ReactNode, type RefObject } from "react";
|
|
2
|
+
import type { PrimitiveProps } from "../internal/primitive-props.js";
|
|
3
|
+
/** AskUser root container. Stateless — consumers manage all state externally. */
|
|
4
|
+
export type AskUserRootProps = PrimitiveProps<"div">;
|
|
5
|
+
export declare const AskUserRoot: ({ className, render, style, ...elementProps }: AskUserRootProps) => import("react").JSX.Element;
|
|
6
|
+
/** Question heading text. Carries the id `Options` uses as its accessible name. */
|
|
7
|
+
export type AskUserLabelProps = PrimitiveProps<"p">;
|
|
8
|
+
export declare const AskUserLabel: ({ className, render, style, ...elementProps }: AskUserLabelProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
9
|
+
/** Row container for `Label` and optional `Navigation`. */
|
|
10
|
+
export type AskUserHeaderProps = PrimitiveProps<"div">;
|
|
11
|
+
export declare const AskUserHeader: ({ className, render, style, ...elementProps }: AskUserHeaderProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
12
|
+
/** Row container for `Previous`, `StepLabel`, and `Next`. */
|
|
13
|
+
export type AskUserNavigationProps = PrimitiveProps<"div">;
|
|
14
|
+
export declare const AskUserNavigation: ({ className, render, style, ...elementProps }: AskUserNavigationProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
15
|
+
/** Navigate to the previous step. Default accessible name; override via aria-label. */
|
|
16
|
+
export type AskUserPreviousProps = PrimitiveProps<"button">;
|
|
17
|
+
export declare const AskUserPrevious: ({ className, render, style, ...elementProps }: AskUserPreviousProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
18
|
+
/** Navigate to the next step. Default accessible name; override via aria-label. */
|
|
19
|
+
export type AskUserNextProps = PrimitiveProps<"button">;
|
|
20
|
+
export declare const AskUserNext: ({ className, render, style, ...elementProps }: AskUserNextProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
21
|
+
/** Step indicator. Reads the current step + total from the composer store; the
|
|
22
|
+
* consumer supplies the text via `children` — either a node, or a callback
|
|
23
|
+
* receiving `{ current, total }` (1-based current). No default format. */
|
|
24
|
+
export type AskUserStepLabelState = {
|
|
25
|
+
current: number;
|
|
26
|
+
total: number;
|
|
27
|
+
};
|
|
28
|
+
export type AskUserStepLabelProps = Omit<PrimitiveProps<"span">, "children"> & {
|
|
29
|
+
children?: ReactNode | ((state: AskUserStepLabelState) => ReactNode);
|
|
30
|
+
};
|
|
31
|
+
export declare const AskUserStepLabel: ({ children, className, render, style, ...elementProps }: AskUserStepLabelProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
32
|
+
/** Fieldset wrapper for `Option` items. Provides `multiSelect`, `groupName`, and highlight state to child options via context. Items self-register on mount (cmdk pattern). */
|
|
33
|
+
type OptionsContextValue = {
|
|
34
|
+
multiSelect: boolean;
|
|
35
|
+
groupName: string;
|
|
36
|
+
highlightedValue: string | null;
|
|
37
|
+
items: RefObject<string[]>;
|
|
38
|
+
register: (value: string, getElement: () => HTMLElement | null) => () => void;
|
|
39
|
+
onItemHover: (value: string) => void;
|
|
40
|
+
};
|
|
41
|
+
export declare const useAskUserOptions: () => OptionsContextValue;
|
|
42
|
+
/** Imperative handle exposed by `AskUser.Options` for keyboard navigation. */
|
|
43
|
+
export type AskUserOptionsHandle = {
|
|
44
|
+
/** Move highlight by direction, focusing the newly highlighted option (roving tabindex). Returns the new highlighted value (null = past the list boundary). */
|
|
45
|
+
navigate: (direction: number) => string | null;
|
|
46
|
+
select: () => {
|
|
47
|
+
value: string;
|
|
48
|
+
} | null;
|
|
49
|
+
clearHighlight: () => void;
|
|
50
|
+
resetHighlight: () => void;
|
|
51
|
+
/** Move DOM focus onto the highlighted option (or the first option when none is highlighted). */
|
|
52
|
+
focusHighlighted: () => void;
|
|
53
|
+
/** The options container element — the composer attaches its question-mode keydown handling here. */
|
|
54
|
+
getElement: () => HTMLElement | null;
|
|
55
|
+
highlightedValue: string | null;
|
|
56
|
+
};
|
|
57
|
+
export type AskUserOptionsProps = Omit<PrimitiveProps<"fieldset">, "value" | "ref"> & {
|
|
58
|
+
/** When true, options render as multi-select (checkbox semantics in the styled layer). */
|
|
59
|
+
multiSelect?: boolean;
|
|
60
|
+
/** Shared `name` attribute for all option inputs in this group. */
|
|
61
|
+
groupName?: string;
|
|
62
|
+
/** Imperative ref for keyboard navigation (navigate, select, clearHighlight, resetHighlight). */
|
|
63
|
+
ref?: RefObject<AskUserOptionsHandle | null>;
|
|
64
|
+
};
|
|
65
|
+
export declare const AskUserOptions: ({ multiSelect, groupName, ref, className, render, style, children, ...elementProps }: AskUserOptionsProps) => import("react").JSX.Element;
|
|
66
|
+
/** Selectable card. Self-registers with parent `Options` on mount (cmdk pattern). Derives highlight state from context. */
|
|
67
|
+
type OptionContextValue = {
|
|
68
|
+
id: string;
|
|
69
|
+
value: string;
|
|
70
|
+
selected: boolean;
|
|
71
|
+
onSelect?: () => void;
|
|
72
|
+
};
|
|
73
|
+
export declare const useAskUserOption: () => OptionContextValue;
|
|
74
|
+
export type AskUserOptionState = {
|
|
75
|
+
/** Present as data-highlighted while keyboard/hover highlighted. */
|
|
76
|
+
highlighted: boolean;
|
|
77
|
+
/** Present as data-selected while the option is selected. */
|
|
78
|
+
selected: boolean;
|
|
79
|
+
};
|
|
80
|
+
export type AskUserOptionProps = PrimitiveProps<"label", AskUserOptionState> & {
|
|
81
|
+
value?: string;
|
|
82
|
+
selected?: boolean;
|
|
83
|
+
onSelect?: () => void;
|
|
84
|
+
};
|
|
85
|
+
export declare const AskUserOption: ({ value, selected, onSelect, className, render, style, children, ...elementProps }: AskUserOptionProps) => import("react").JSX.Element;
|
|
86
|
+
/** Flex column wrapper for `OptionLabel` and `OptionDescription`. */
|
|
87
|
+
export type AskUserOptionContentProps = PrimitiveProps<"span">;
|
|
88
|
+
export declare const AskUserOptionContent: ({ className, render, style, ...elementProps }: AskUserOptionContentProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
89
|
+
/** Option title text. */
|
|
90
|
+
export type AskUserOptionLabelProps = PrimitiveProps<"span">;
|
|
91
|
+
export declare const AskUserOptionLabel: ({ className, render, style, ...elementProps }: AskUserOptionLabelProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
92
|
+
/** Option subtitle/description text. */
|
|
93
|
+
export type AskUserOptionDescriptionProps = PrimitiveProps<"span">;
|
|
94
|
+
export declare const AskUserOptionDescription: ({ className, render, style, ...elementProps }: AskUserOptionDescriptionProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
95
|
+
/** Keyboard shortcut hints displayed below the ask-user options. */
|
|
96
|
+
export type AskUserHintsProps = PrimitiveProps<"div">;
|
|
97
|
+
export declare const AskUserHints: ({ className, render, style, ...elementProps }: AskUserHintsProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
98
|
+
/** Dismiss/skip button. Stateless — wire `onClick` to your dismiss handler. */
|
|
99
|
+
export type AskUserDismissProps = PrimitiveProps<"button">;
|
|
100
|
+
export declare const AskUserDismiss: ({ className, render, style, ...elementProps }: AskUserDismissProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
101
|
+
/** Continue/submit button — `type=submit` so the enclosing form drives it. */
|
|
102
|
+
export type AskUserContinueProps = PrimitiveProps<"button">;
|
|
103
|
+
export declare const AskUserContinue: ({ className, render, style, ...elementProps }: AskUserContinueProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
104
|
+
export {};
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
// Headless ask-user questionnaire parts. Owns the option registration system
|
|
4
|
+
// (cmdk pattern), highlight state, and the imperative keyboard-navigation
|
|
5
|
+
// handle. Input widgets (checkbox/radio), icons, and the answered summary
|
|
6
|
+
// belong to the styled layer, which reads state via useAskUserOptions() /
|
|
7
|
+
// useAskUserOption(). Every part supports the Base UI render prop.
|
|
8
|
+
import { createContext, use, useCallback, useId, useImperativeHandle, useInsertionEffect, useLayoutEffect, useMemo, useRef, useState, } from "react";
|
|
9
|
+
import { useComposer } from "../composer/store.js";
|
|
10
|
+
import { useRenderElement } from "../internal/render/useRenderElement.js";
|
|
11
|
+
import { scrollIntoViewWithin } from "../internal/scroll-into-view-within.js";
|
|
12
|
+
const AskUserIdsContext = createContext({ labelId: "", stepLabelId: "" });
|
|
13
|
+
export const AskUserRoot = ({ className, render, style, ...elementProps }) => {
|
|
14
|
+
const labelId = useId();
|
|
15
|
+
const stepLabelId = useId();
|
|
16
|
+
const element = useRenderElement("div", { className, render, style }, { props: [{ "data-ask-user": "" }, elementProps] });
|
|
17
|
+
const ids = useMemo(() => ({ labelId, stepLabelId }), [labelId, stepLabelId]);
|
|
18
|
+
return _jsx(AskUserIdsContext, { value: ids, children: element });
|
|
19
|
+
};
|
|
20
|
+
export const AskUserLabel = ({ className, render, style, ...elementProps }) => {
|
|
21
|
+
const { labelId } = use(AskUserIdsContext);
|
|
22
|
+
return useRenderElement("p", { className, render, style }, { props: [{ id: labelId || undefined, "data-ask-user-label": "" }, elementProps] });
|
|
23
|
+
};
|
|
24
|
+
export const AskUserHeader = ({ className, render, style, ...elementProps }) => useRenderElement("div", { className, render, style }, { props: [{ "data-ask-user-header": "" }, elementProps] });
|
|
25
|
+
export const AskUserNavigation = ({ className, render, style, ...elementProps }) => useRenderElement("div", { className, render, style }, { props: [{ "data-ask-user-navigation": "" }, elementProps] });
|
|
26
|
+
export const AskUserPrevious = ({ className, render, style, ...elementProps }) => useRenderElement("button", { className, render, style }, { props: [{ "aria-label": "Previous question", "data-ask-user-previous": "" }, elementProps] });
|
|
27
|
+
export const AskUserNext = ({ className, render, style, ...elementProps }) => useRenderElement("button", { className, render, style }, { props: [{ "aria-label": "Next question", "data-ask-user-next": "" }, elementProps] });
|
|
28
|
+
export const AskUserStepLabel = ({ children, className, render, style, ...elementProps }) => {
|
|
29
|
+
const current = useComposer((composer) => composer.askUser.step) + 1;
|
|
30
|
+
const total = useComposer((composer) => composer.askUser.questions?.length ?? 0);
|
|
31
|
+
const { stepLabelId } = use(AskUserIdsContext);
|
|
32
|
+
const content = typeof children === "function" ? children({ current, total }) : children;
|
|
33
|
+
return useRenderElement("span", { className, render, style }, {
|
|
34
|
+
props: [
|
|
35
|
+
{ id: stepLabelId || undefined, "data-ask-user-step-label": "", children: content },
|
|
36
|
+
elementProps,
|
|
37
|
+
],
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
const OptionsContext = createContext({
|
|
41
|
+
multiSelect: false,
|
|
42
|
+
groupName: "",
|
|
43
|
+
highlightedValue: null,
|
|
44
|
+
items: { current: [] },
|
|
45
|
+
register: () => () => { },
|
|
46
|
+
onItemHover: () => { },
|
|
47
|
+
});
|
|
48
|
+
export const useAskUserOptions = () => use(OptionsContext);
|
|
49
|
+
export const AskUserOptions = ({ multiSelect = false, groupName = "", ref, className, render, style, children, ...elementProps }) => {
|
|
50
|
+
const registeredItems = useRef([]);
|
|
51
|
+
const itemElements = useRef(new Map());
|
|
52
|
+
const containerRef = useRef(null);
|
|
53
|
+
const { labelId, stepLabelId } = use(AskUserIdsContext);
|
|
54
|
+
const [highlightedValue, setHighlightedValue] = useState(null);
|
|
55
|
+
// Track highlight in a ref so imperative methods see latest value without re-binding.
|
|
56
|
+
// Mirrored after commit, not during a render React may discard, and in an
|
|
57
|
+
// insertion effect: it runs before every layout effect in the tree, and the
|
|
58
|
+
// options register in theirs, where they may seed the highlight imperatively.
|
|
59
|
+
const highlightedValueRef = useRef(highlightedValue);
|
|
60
|
+
useInsertionEffect(() => {
|
|
61
|
+
highlightedValueRef.current = highlightedValue;
|
|
62
|
+
});
|
|
63
|
+
const register = useCallback((itemValue, getElement) => {
|
|
64
|
+
registeredItems.current = [...registeredItems.current, itemValue];
|
|
65
|
+
itemElements.current.set(itemValue, getElement);
|
|
66
|
+
// Auto-highlight whenever an item registers while nothing is highlighted.
|
|
67
|
+
// Using length === 1 here is fragile: during a step transition, React can
|
|
68
|
+
// interleave old-item cleanups with new-item setups, so the new first
|
|
69
|
+
// option may arrive when the list is not exactly length 1.
|
|
70
|
+
if (highlightedValueRef.current === null) {
|
|
71
|
+
highlightedValueRef.current = itemValue;
|
|
72
|
+
setHighlightedValue(itemValue);
|
|
73
|
+
}
|
|
74
|
+
return () => {
|
|
75
|
+
registeredItems.current = registeredItems.current.filter((v) => v !== itemValue);
|
|
76
|
+
itemElements.current.delete(itemValue);
|
|
77
|
+
// Clear highlight when the highlighted item deregisters — allows auto-highlight
|
|
78
|
+
// to fire for the next set of items (e.g. on step change)
|
|
79
|
+
if (highlightedValueRef.current === itemValue) {
|
|
80
|
+
highlightedValueRef.current = null;
|
|
81
|
+
setHighlightedValue(null);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}, []);
|
|
85
|
+
const onItemHover = useCallback((itemValue) => {
|
|
86
|
+
setHighlightedValue(itemValue);
|
|
87
|
+
}, []);
|
|
88
|
+
// Roving tabindex: DOM focus follows the highlight for keyboard navigation.
|
|
89
|
+
// Focus without the browser's scroll, which would also move the page around
|
|
90
|
+
// an embedded question; bring the option into view within the question's
|
|
91
|
+
// own scroll area instead.
|
|
92
|
+
const focusValue = useCallback((itemValue) => {
|
|
93
|
+
if (itemValue === null)
|
|
94
|
+
return;
|
|
95
|
+
const element = itemElements.current.get(itemValue)?.();
|
|
96
|
+
if (!element)
|
|
97
|
+
return;
|
|
98
|
+
element.focus({ preventScroll: true });
|
|
99
|
+
const boundary = element.closest("[data-ask-user]") ?? element;
|
|
100
|
+
scrollIntoViewWithin(element.getBoundingClientRect(), element, boundary);
|
|
101
|
+
}, []);
|
|
102
|
+
useImperativeHandle(ref, () => ({
|
|
103
|
+
navigate: (direction) => {
|
|
104
|
+
const items = registeredItems.current;
|
|
105
|
+
if (items.length === 0)
|
|
106
|
+
return null;
|
|
107
|
+
const previous = highlightedValueRef.current;
|
|
108
|
+
let next;
|
|
109
|
+
if (previous === null) {
|
|
110
|
+
next = (direction > 0 ? items[0] : items[items.length - 1]) ?? null;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
const currentIndex = items.indexOf(previous);
|
|
114
|
+
const nextIndex = currentIndex + direction;
|
|
115
|
+
if (nextIndex >= items.length || nextIndex < 0)
|
|
116
|
+
next = null;
|
|
117
|
+
else
|
|
118
|
+
next = items[nextIndex] ?? null;
|
|
119
|
+
}
|
|
120
|
+
highlightedValueRef.current = next;
|
|
121
|
+
setHighlightedValue(next);
|
|
122
|
+
focusValue(next);
|
|
123
|
+
return next;
|
|
124
|
+
},
|
|
125
|
+
select: () => {
|
|
126
|
+
const current = highlightedValueRef.current;
|
|
127
|
+
if (current === null)
|
|
128
|
+
return null;
|
|
129
|
+
return { value: current };
|
|
130
|
+
},
|
|
131
|
+
clearHighlight: () => {
|
|
132
|
+
highlightedValueRef.current = null;
|
|
133
|
+
setHighlightedValue(null);
|
|
134
|
+
},
|
|
135
|
+
resetHighlight: () => {
|
|
136
|
+
// Clear synchronously — the auto-highlight path in `register` will pick
|
|
137
|
+
// up the first new item when it mounts after a step change.
|
|
138
|
+
highlightedValueRef.current = null;
|
|
139
|
+
setHighlightedValue(null);
|
|
140
|
+
},
|
|
141
|
+
focusHighlighted: () => {
|
|
142
|
+
const target = highlightedValueRef.current ?? registeredItems.current[0] ?? null;
|
|
143
|
+
if (target !== null && highlightedValueRef.current === null) {
|
|
144
|
+
highlightedValueRef.current = target;
|
|
145
|
+
setHighlightedValue(target);
|
|
146
|
+
}
|
|
147
|
+
focusValue(target);
|
|
148
|
+
},
|
|
149
|
+
getElement: () => containerRef.current,
|
|
150
|
+
get highlightedValue() {
|
|
151
|
+
return highlightedValueRef.current;
|
|
152
|
+
},
|
|
153
|
+
}), [focusValue]);
|
|
154
|
+
const element = useRenderElement("fieldset", { className, render, style }, {
|
|
155
|
+
ref: containerRef,
|
|
156
|
+
props: [
|
|
157
|
+
{
|
|
158
|
+
// The options are the real selectable controls (role radio/checkbox
|
|
159
|
+
// on each option), so the container is their labelled group.
|
|
160
|
+
role: multiSelect ? "group" : "radiogroup",
|
|
161
|
+
"aria-labelledby": labelId || undefined,
|
|
162
|
+
"aria-describedby": stepLabelId || undefined,
|
|
163
|
+
"data-ask-user-options": "",
|
|
164
|
+
children,
|
|
165
|
+
},
|
|
166
|
+
elementProps,
|
|
167
|
+
],
|
|
168
|
+
});
|
|
169
|
+
const options = useMemo(() => ({
|
|
170
|
+
multiSelect,
|
|
171
|
+
groupName,
|
|
172
|
+
highlightedValue,
|
|
173
|
+
items: registeredItems,
|
|
174
|
+
register,
|
|
175
|
+
onItemHover,
|
|
176
|
+
}), [multiSelect, groupName, highlightedValue, register, onItemHover]);
|
|
177
|
+
return _jsx(OptionsContext, { value: options, children: element });
|
|
178
|
+
};
|
|
179
|
+
const OptionContext = createContext({
|
|
180
|
+
id: "",
|
|
181
|
+
value: "",
|
|
182
|
+
selected: false,
|
|
183
|
+
onSelect: () => { },
|
|
184
|
+
});
|
|
185
|
+
export const useAskUserOption = () => use(OptionContext);
|
|
186
|
+
export const AskUserOption = ({ value = "", selected = false, onSelect, className, render, style, children, ...elementProps }) => {
|
|
187
|
+
const id = useId();
|
|
188
|
+
const { multiSelect, highlightedValue, register, onItemHover } = use(OptionsContext);
|
|
189
|
+
const optionRef = useRef(null);
|
|
190
|
+
const isHighlighted = value === highlightedValue;
|
|
191
|
+
// Self-register on mount, deregister on unmount (true subscription side effect)
|
|
192
|
+
const registerRef = useRef(register);
|
|
193
|
+
useInsertionEffect(() => {
|
|
194
|
+
registerRef.current = register;
|
|
195
|
+
});
|
|
196
|
+
useItemRegistration(value, registerRef, optionRef);
|
|
197
|
+
const element = useRenderElement("label", { className, render, style }, {
|
|
198
|
+
ref: optionRef,
|
|
199
|
+
state: { highlighted: isHighlighted, selected },
|
|
200
|
+
props: [
|
|
201
|
+
{
|
|
202
|
+
// The option IS the selectable control: role + checked state live
|
|
203
|
+
// here, and the roving tabindex makes the highlighted option the
|
|
204
|
+
// group's single tab stop. Never a native input — the composer's
|
|
205
|
+
// question-mode key scoping treats inputs as foreign editables.
|
|
206
|
+
id,
|
|
207
|
+
role: multiSelect ? "checkbox" : "radio",
|
|
208
|
+
"aria-checked": selected,
|
|
209
|
+
tabIndex: isHighlighted ? 0 : -1,
|
|
210
|
+
"data-ask-user-option": "",
|
|
211
|
+
onMouseMove: () => onItemHover(value),
|
|
212
|
+
// Keep highlight and DOM focus unified when focus arrives by other
|
|
213
|
+
// means (Tab into the group, SR virtual-cursor activation).
|
|
214
|
+
onFocus: () => onItemHover(value),
|
|
215
|
+
// Self-contained click selection. The option itself is the control
|
|
216
|
+
// now, so only a *different* nested interactive element defers —
|
|
217
|
+
// a bare option stays fully clickable.
|
|
218
|
+
onClick: (event) => {
|
|
219
|
+
const nested = event.target.closest("input,button,[role=checkbox],[role=radio]");
|
|
220
|
+
if (nested && nested !== event.currentTarget)
|
|
221
|
+
return;
|
|
222
|
+
event.preventDefault();
|
|
223
|
+
onSelect?.();
|
|
224
|
+
},
|
|
225
|
+
children,
|
|
226
|
+
},
|
|
227
|
+
elementProps,
|
|
228
|
+
],
|
|
229
|
+
});
|
|
230
|
+
const option = useMemo(() => ({ id, value, selected, onSelect }), [id, value, selected, onSelect]);
|
|
231
|
+
return _jsx(OptionContext, { value: option, children: element });
|
|
232
|
+
};
|
|
233
|
+
/** Registers an item value + element with the parent Options container synchronously before paint and deregisters on unmount. */
|
|
234
|
+
const useItemRegistration = (value, registerRef, elementRef) => {
|
|
235
|
+
// useLayoutEffect ensures items are registered before paint so the initial highlight resolves immediately
|
|
236
|
+
useLayoutEffect(() => {
|
|
237
|
+
return registerRef.current(value, () => elementRef.current);
|
|
238
|
+
}, [value, registerRef, elementRef]);
|
|
239
|
+
};
|
|
240
|
+
export const AskUserOptionContent = ({ className, render, style, ...elementProps }) => useRenderElement("span", { className, render, style }, { props: [{ "data-ask-user-option-content": "" }, elementProps] });
|
|
241
|
+
export const AskUserOptionLabel = ({ className, render, style, ...elementProps }) => useRenderElement("span", { className, render, style }, { props: [{ "data-ask-user-option-label": "" }, elementProps] });
|
|
242
|
+
export const AskUserOptionDescription = ({ className, render, style, ...elementProps }) => useRenderElement("span", { className, render, style }, { props: [{ "data-ask-user-option-description": "" }, elementProps] });
|
|
243
|
+
export const AskUserHints = ({ className, render, style, ...elementProps }) => useRenderElement("div", { className, render, style }, { props: [{ "data-ask-user-hints": "" }, elementProps] });
|
|
244
|
+
export const AskUserDismiss = ({ className, render, style, ...elementProps }) => useRenderElement("button", { className, render, style }, { props: [{ type: "button", "data-ask-user-dismiss": "" }, elementProps] });
|
|
245
|
+
export const AskUserContinue = ({ className, render, style, ...elementProps }) => useRenderElement("button", { className, render, style }, { props: [{ type: "submit", "data-ask-user-continue": "" }, elementProps] });
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export type { AskUserContinueProps, AskUserDismissProps, AskUserHeaderProps, AskUserHintsProps, AskUserLabelProps, AskUserNavigationProps, AskUserNextProps, AskUserOptionContentProps, AskUserOptionDescriptionProps, AskUserOptionLabelProps, AskUserOptionProps, AskUserOptionState, AskUserOptionsHandle, AskUserOptionsProps, AskUserPreviousProps, AskUserRootProps, AskUserStepLabelProps, AskUserStepLabelState, } from "./ask-user.js";
|
|
2
|
+
export { useAskUserOption, useAskUserOptions } from "./ask-user.js";
|
|
3
|
+
export * as AskUser from "./index.parts.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { AskUserContinue as Continue, AskUserDismiss as Dismiss, AskUserHeader as Header, AskUserHints as Hints, AskUserLabel as Label, AskUserNavigation as Navigation, AskUserNext as Next, AskUserOption as Option, AskUserOptionContent as OptionContent, AskUserOptionDescription as OptionDescription, AskUserOptionLabel as OptionLabel, AskUserOptions as Options, AskUserPrevious as Previous, AskUserRoot as Root, AskUserStepLabel as StepLabel, } from "./ask-user.js";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Part names for the `AskUser` namespace. Deliberately carries no
|
|
2
|
+
// "use client" directive: this module and index.ts must stay server-resolvable
|
|
3
|
+
// so a React Server Component can reach AskUser.Root and its siblings
|
|
4
|
+
// through them. The directive lives one level down, on ask-user.tsx.
|
|
5
|
+
export { AskUserContinue as Continue, AskUserDismiss as Dismiss, AskUserHeader as Header, AskUserHints as Hints, AskUserLabel as Label, AskUserNavigation as Navigation, AskUserNext as Next, AskUserOption as Option, AskUserOptionContent as OptionContent, AskUserOptionDescription as OptionDescription, AskUserOptionLabel as OptionLabel, AskUserOptions as Options, AskUserPrevious as Previous, AskUserRoot as Root, AskUserStepLabel as StepLabel, } from "./ask-user.js";
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { PrimitiveProps } from "../internal/primitive-props.js";
|
|
3
|
+
export type AttachmentItem = {
|
|
4
|
+
id: string;
|
|
5
|
+
url: string;
|
|
6
|
+
filename?: string;
|
|
7
|
+
mediaType?: string;
|
|
8
|
+
fileSize?: number;
|
|
9
|
+
};
|
|
10
|
+
export type AttachmentErrorCode = "accept" | "max_file_size" | "max_files";
|
|
11
|
+
export declare const toAttachmentItem: (file: File) => AttachmentItem;
|
|
12
|
+
export declare const revokeAttachmentUrl: (item: Pick<AttachmentItem, "url">) => void;
|
|
13
|
+
export declare const revokeAllAttachmentUrls: (items: Pick<AttachmentItem, "url">[]) => void;
|
|
14
|
+
export declare const matchesAccept: (file: File, accept: string) => boolean;
|
|
15
|
+
export type AttachmentsRootProps = PrimitiveProps<"div">;
|
|
16
|
+
/**
|
|
17
|
+
* The tray container. Mount it only when there are items to show; it renders
|
|
18
|
+
* no layout of its own.
|
|
19
|
+
* Renders a `<div>` element.
|
|
20
|
+
*/
|
|
21
|
+
export declare const AttachmentsRoot: ({ className, render, style, ...elementProps }: AttachmentsRootProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
22
|
+
export type AttachmentsItemProps = PrimitiveProps<"div">;
|
|
23
|
+
/**
|
|
24
|
+
* One attachment, as a structural slot with no media taxonomy of its own.
|
|
25
|
+
* Read the item's `mediaType` and decide what an image or a PDF looks like.
|
|
26
|
+
* Renders a `<div>` element.
|
|
27
|
+
*/
|
|
28
|
+
export declare const AttachmentsItem: ({ className, render, style, ...elementProps }: AttachmentsItemProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
29
|
+
export type AttachmentsRemoveProps = PrimitiveProps<"button"> & {
|
|
30
|
+
onRemove: () => void;
|
|
31
|
+
/** Names the control per item ("Remove report.pdf") — without it, N remove buttons announce identically. */
|
|
32
|
+
filename?: string;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* The removal affordance. Named "Remove attachment" by default, or
|
|
36
|
+
* "Remove {filename}" when `filename` is set, so a row of them does not
|
|
37
|
+
* announce identically.
|
|
38
|
+
* Renders a `<button>` element.
|
|
39
|
+
*/
|
|
40
|
+
export declare const AttachmentsRemove: ({ onRemove, filename, className, render, style, ...elementProps }: AttachmentsRemoveProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
41
|
+
export type AttachmentsDropzoneState = {
|
|
42
|
+
/** Present as data-visible while files are dragged over the scope. */
|
|
43
|
+
visible: boolean;
|
|
44
|
+
};
|
|
45
|
+
export type AttachmentsDropzoneProps = PrimitiveProps<"div", AttachmentsDropzoneState> & {
|
|
46
|
+
visible?: boolean;
|
|
47
|
+
/** Keep the dropzone mounted (hidden) when not visible. */
|
|
48
|
+
keepMounted?: boolean;
|
|
49
|
+
/** CSS selector to portal the dropzone into (e.g. an app shell region). */
|
|
50
|
+
portalSelector?: string;
|
|
51
|
+
children?: ReactNode;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* The drop overlay. `portalSelector` only moves where it renders (e.g. over an
|
|
55
|
+
* app shell region); where files can be dropped is `Composer.Attachments`'
|
|
56
|
+
* `globalDrop`, not this part.
|
|
57
|
+
* Renders a `<div>` element.
|
|
58
|
+
*/
|
|
59
|
+
export declare const AttachmentsDropzone: ({ visible, keepMounted, portalSelector, children, className, render, style, ...elementProps }: AttachmentsDropzoneProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | null;
|
|
60
|
+
export type AttachmentsErrorProps = PrimitiveProps<"span">;
|
|
61
|
+
/**
|
|
62
|
+
* The validation slot, as a live region: whatever appears inside announces
|
|
63
|
+
* immediately. Validation emits a code, never copy, so the message is yours.
|
|
64
|
+
* Renders a `<span>` element with `role="alert"`.
|
|
65
|
+
*/
|
|
66
|
+
export declare const AttachmentsError: ({ className, render, style, ...elementProps }: AttachmentsErrorProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
67
|
+
export type AttachmentsTriggerProps = PrimitiveProps<"button">;
|
|
68
|
+
/**
|
|
69
|
+
* The file-picker button, named "Add attachment" by default. The package owns
|
|
70
|
+
* no file input; wire this to your own.
|
|
71
|
+
* Renders a `<button>` element.
|
|
72
|
+
*/
|
|
73
|
+
export declare const AttachmentsTrigger: ({ className, render, style, ...elementProps }: AttachmentsTriggerProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|