@particle-academy/react-fancy 5.70.1 → 5.71.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/CHANGELOG.md +25 -0
- package/README.md +1 -0
- package/dist/autocomplete.cjs +3 -3
- package/dist/autocomplete.js +2 -2
- package/dist/chat-thread.cjs +27 -0
- package/dist/chat-thread.cjs.map +1 -0
- package/dist/chat-thread.d.cts +260 -0
- package/dist/chat-thread.d.ts +260 -0
- package/dist/chat-thread.js +14 -0
- package/dist/chat-thread.js.map +1 -0
- package/dist/chunk-5IE65DIH.cjs +623 -0
- package/dist/chunk-5IE65DIH.cjs.map +1 -0
- package/dist/{chunk-6QAF35AJ.cjs → chunk-7JLKOVHX.cjs} +3 -3
- package/dist/{chunk-6QAF35AJ.cjs.map → chunk-7JLKOVHX.cjs.map} +1 -1
- package/dist/{chunk-TCMOD3WW.js → chunk-A6NYZ3F5.js} +3 -3
- package/dist/{chunk-TCMOD3WW.js.map → chunk-A6NYZ3F5.js.map} +1 -1
- package/dist/{chunk-6EA3DSPT.cjs → chunk-FGOFG2UZ.cjs} +5 -5
- package/dist/{chunk-6EA3DSPT.cjs.map → chunk-FGOFG2UZ.cjs.map} +1 -1
- package/dist/{chunk-FHRVSYJU.cjs → chunk-MP55W37V.cjs} +3 -3
- package/dist/{chunk-FHRVSYJU.cjs.map → chunk-MP55W37V.cjs.map} +1 -1
- package/dist/{chunk-EZL5DILH.js → chunk-OPDHSOUN.js} +3 -3
- package/dist/{chunk-EZL5DILH.js.map → chunk-OPDHSOUN.js.map} +1 -1
- package/dist/chunk-UWYSJARZ.js +620 -0
- package/dist/chunk-UWYSJARZ.js.map +1 -0
- package/dist/{chunk-M6UAVA2I.js → chunk-XIA2UAVO.js} +3 -3
- package/dist/{chunk-M6UAVA2I.js.map → chunk-XIA2UAVO.js.map} +1 -1
- package/dist/editor.cjs +3 -3
- package/dist/editor.js +3 -3
- package/dist/index.cjs +170 -161
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +26 -25
- package/dist/index.js.map +1 -1
- package/dist/input.cjs +1 -1
- package/dist/input.js +1 -1
- package/dist/inputs.cjs +7 -7
- package/dist/inputs.js +3 -3
- package/dist/json-editor.cjs +16 -16
- package/dist/json-editor.js +5 -5
- package/dist/prompt-input.cjs +1 -1
- package/dist/prompt-input.js +1 -1
- package/dist/select.cjs +3 -3
- package/dist/select.js +2 -2
- package/docs/ChatThread.md +318 -0
- package/package.json +11 -1
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
# ChatThread
|
|
2
|
+
|
|
3
|
+
A controlled message list for agent and support conversations: chat with an
|
|
4
|
+
agent in a side panel, a ticket thread with internal notes, an admin chat whose
|
|
5
|
+
agent drafts proposals for a human to approve. `ChatMessage` is the single
|
|
6
|
+
message, usable on its own.
|
|
7
|
+
|
|
8
|
+
Added in `5.71.0`.
|
|
9
|
+
|
|
10
|
+
## Import
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { ChatThread, ChatMessage } from "@particle-academy/react-fancy";
|
|
14
|
+
// or the subpath
|
|
15
|
+
import { ChatThread } from "@particle-academy/react-fancy/chat-thread";
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Basic usage
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
const [messages, setMessages] = useState<ChatMessageData[]>([]);
|
|
22
|
+
|
|
23
|
+
<ChatThread
|
|
24
|
+
className="h-full"
|
|
25
|
+
messages={messages}
|
|
26
|
+
composer={
|
|
27
|
+
<Composer
|
|
28
|
+
onSubmit={async (text) => {
|
|
29
|
+
const res = await send(text); // the server appends the message
|
|
30
|
+
if (!res.ok) return { ok: false, error: res.error };
|
|
31
|
+
}}
|
|
32
|
+
/>
|
|
33
|
+
}
|
|
34
|
+
/>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The thread renders exactly `messages`, oldest first. It owns no message state.
|
|
38
|
+
It does not ship an input: put `Composer` or `PromptInput` in the `composer`
|
|
39
|
+
slot. Both have acknowledged send (5.70.0), so the draft stays until your
|
|
40
|
+
`onSubmit` Promise resolves.
|
|
41
|
+
|
|
42
|
+
## Messages are JSON
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
interface ChatMessageData {
|
|
46
|
+
id: string; // data-chat-message
|
|
47
|
+
role: "user" | "assistant" | "system" | "agent" | "staff" | "customer" | string;
|
|
48
|
+
author?: { name: string; avatar?: string };
|
|
49
|
+
content: string; // markdown, always sanitised
|
|
50
|
+
format?: "markdown" | "text"; // "text" renders literally
|
|
51
|
+
createdAt: string | number; // ISO 8601 or epoch ms
|
|
52
|
+
status?: "pending" | "streaming" | "sent" | "failed"; // default "sent"
|
|
53
|
+
visibility?: "public" | "internal"; // default "public"
|
|
54
|
+
attachments?: { id: string; name: string; url?: string; mimeType?: string; size?: number }[];
|
|
55
|
+
proposal?: { id: string; title?: string; state?: "pending" | "accepted" | "rejected" | "withdrawn";
|
|
56
|
+
acceptLabel?: string; rejectLabel?: string };
|
|
57
|
+
own?: boolean; // default: ownRoles.includes(role)
|
|
58
|
+
meta?: Record<string, unknown>; // carried, never rendered
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
- **Content** goes through `ContentRenderer` with sanitisation on. There is no
|
|
63
|
+
`unsafe` prop on the thread, on purpose: message bodies are text the host did
|
|
64
|
+
not author. `contentOptions` passes `extensions`, `copyButtons`, `onCopy`,
|
|
65
|
+
`clipboardWriter` and `lineSpacing` through.
|
|
66
|
+
- **Streaming.** A message with `status: "streaming"` renders with
|
|
67
|
+
`ContentRenderer`'s `streaming`: by default an unfinished code fence or tag is
|
|
68
|
+
held back until it closes. `streaming="progressive"` on the thread shows it
|
|
69
|
+
as it fills. The message is `aria-busy` while it streams, so screen readers
|
|
70
|
+
are not read every token. Set the status to `"sent"` when the stream ends.
|
|
71
|
+
- **Roles.** The six named roles get default labels. Any other string is
|
|
72
|
+
carried verbatim on `data-chat-role` and rendered neutrally. `system` renders
|
|
73
|
+
as a centred line.
|
|
74
|
+
- **Own messages** align to the end. `ownRoles` (default `["user"]`) decides
|
|
75
|
+
which; `own` on a message overrides it. A support staff view passes
|
|
76
|
+
`ownRoles={["staff"]}`, or sets `own` per message to distinguish colleagues.
|
|
77
|
+
- **Attachments** render as links (unsafe schemes render the name without a
|
|
78
|
+
link). They are never loaded inline. `renderAttachment` replaces one.
|
|
79
|
+
|
|
80
|
+
## Internal notes
|
|
81
|
+
|
|
82
|
+
`visibility: "internal"` renders a dashed amber bubble with a visible
|
|
83
|
+
"Internal note" label (`[data-chat-internal-label]`) on every internal message.
|
|
84
|
+
|
|
85
|
+
**The thread never decides visibility.** It renders exactly what it is given.
|
|
86
|
+
Filter on the server (and in the transformer) and pass only what this viewer
|
|
87
|
+
may see. A component that hid internal notes itself would be a second policy
|
|
88
|
+
nobody tests.
|
|
89
|
+
|
|
90
|
+
## Proposals (approval cards)
|
|
91
|
+
|
|
92
|
+
A message with a `proposal` is an agent's suggested action awaiting a human: a
|
|
93
|
+
drafted reply, a field change, a page edit. Its `content` is the draft.
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
<ChatThread
|
|
97
|
+
messages={messages}
|
|
98
|
+
onProposalAccept={(m) => api.decide(m.proposal!.id, "accept", m.meta?.version)}
|
|
99
|
+
onProposalReject={(m) => openRejectReasonDialog(m)}
|
|
100
|
+
/>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
- Stable handle `[data-chat-proposal="<proposal id>"]` with
|
|
104
|
+
`data-chat-proposal-state`; buttons are
|
|
105
|
+
`[data-chat-proposal-action="accept" | "reject"]`.
|
|
106
|
+
- **It never acts on its own.** Nothing is called on render. The state is the
|
|
107
|
+
host's: buttons show only while `state` is `"pending"` (the default) and only
|
|
108
|
+
for the callbacks you pass. When the host moves the card to `accepted`,
|
|
109
|
+
`rejected` or `withdrawn`, the buttons are replaced by a status badge.
|
|
110
|
+
- **Acknowledged.** Return a Promise and both buttons are disabled
|
|
111
|
+
(`aria-busy`) until it settles; a second decision is refused even within the
|
|
112
|
+
same event batch. A rejection goes to `onActionError(error, action)`; without
|
|
113
|
+
it the rejection is left unhandled so it still reaches the console.
|
|
114
|
+
- "Edit then approve" and "reject with a reason" are host UI: open your editor
|
|
115
|
+
or dialog from the callback, or from the actions slot.
|
|
116
|
+
|
|
117
|
+
## Scrolling: stick to the bottom without yanking
|
|
118
|
+
|
|
119
|
+
- The thread starts at the bottom and follows new messages, and a streaming
|
|
120
|
+
message as it grows, **only while the reader is at the bottom**
|
|
121
|
+
(`bottomThreshold`, default 48px).
|
|
122
|
+
- A reader who scrolled up stays exactly where they are. An
|
|
123
|
+
"N new messages" control (`[data-react-fancy-chat-thread-jump]`,
|
|
124
|
+
`data-unseen`) appears; it jumps to the latest and moves focus to the log.
|
|
125
|
+
- The viewer's own new message (see `ownRoles`) always scrolls into view.
|
|
126
|
+
- Prepending earlier messages keeps the message under the reader in place.
|
|
127
|
+
- **Only the log's own `scrollTop` is ever set.** The thread never calls
|
|
128
|
+
`scrollIntoView`, and it focuses with `{ preventScroll: true }`, so no
|
|
129
|
+
ancestor and never the page scrolls (the rule `PromptInput` adopted in 5.69.0
|
|
130
|
+
for genie#1290).
|
|
131
|
+
- `stickToBottom={false}` turns following off entirely.
|
|
132
|
+
- `onAtBottomChange(atBottom)` reports the reader arriving at or leaving the
|
|
133
|
+
bottom.
|
|
134
|
+
|
|
135
|
+
### Imperative handle
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
const thread = useRef<ChatThreadHandle>(null);
|
|
139
|
+
<ChatThread ref={thread} messages={messages} />;
|
|
140
|
+
|
|
141
|
+
thread.current?.scrollToLatest();
|
|
142
|
+
thread.current?.scrollToMessage("msg_42"); // false if not rendered
|
|
143
|
+
thread.current?.element; // the log element
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Load earlier
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
<ChatThread
|
|
150
|
+
messages={messages}
|
|
151
|
+
hasEarlier={cursor !== null}
|
|
152
|
+
onLoadEarlier={async () => {
|
|
153
|
+
const page = await api.history(cursor);
|
|
154
|
+
setMessages((m) => [...page.items, ...m]);
|
|
155
|
+
setCursor(page.next);
|
|
156
|
+
}}
|
|
157
|
+
/>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
The control shows while `onLoadEarlier` is set and `hasEarlier` is not
|
|
161
|
+
`false`. A returned Promise disables it until it settles; `loadingEarlier`
|
|
162
|
+
drives the same state from the host.
|
|
163
|
+
|
|
164
|
+
## Day separators and grouping
|
|
165
|
+
|
|
166
|
+
```tsx
|
|
167
|
+
<ChatThread messages={messages} separators="day" groupConsecutive />
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
- `separators="day"` inserts `[data-chat-day-separator="YYYY-MM-DD"]` at each
|
|
171
|
+
local calendar-day change. `formatDay` formats the label.
|
|
172
|
+
- `groupConsecutive` merges consecutive messages from the same role and author
|
|
173
|
+
within 5 minutes (or pass a window in ms): the header and avatar show once
|
|
174
|
+
and later messages carry `data-chat-grouped`. Internal and public messages
|
|
175
|
+
never merge, and neither do proposals or system lines.
|
|
176
|
+
|
|
177
|
+
## Per-message actions
|
|
178
|
+
|
|
179
|
+
```tsx
|
|
180
|
+
<ChatThread
|
|
181
|
+
messages={messages}
|
|
182
|
+
renderActions={(m) =>
|
|
183
|
+
m.role === "assistant" ? (
|
|
184
|
+
<Button size="xs" variant="ghost" icon="copy" onClick={() => copy(m.content)}>Copy</Button>
|
|
185
|
+
) : m.status === "failed" ? (
|
|
186
|
+
<Button size="xs" variant="ghost" onClick={() => retry(m)}>Retry</Button>
|
|
187
|
+
) : null
|
|
188
|
+
}
|
|
189
|
+
/>
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
The slot renders in `[data-chat-message-actions]`; return `null` for none.
|
|
193
|
+
|
|
194
|
+
## Human+
|
|
195
|
+
|
|
196
|
+
- **Controlled.** `messages` is the state. Loading history and deciding a
|
|
197
|
+
proposal are callbacks; the thread holds only view state (scroll position,
|
|
198
|
+
the unseen count).
|
|
199
|
+
- **Stable handles.** `data-react-fancy-chat-thread`,
|
|
200
|
+
`data-chat-message="<id>"` with `data-chat-role`, `data-chat-status`,
|
|
201
|
+
`data-chat-visibility`, and `data-chat-proposal="<id>"`.
|
|
202
|
+
- **Observable activity.** `onActivity` receives every interaction:
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
type ChatThreadActivity =
|
|
206
|
+
| { type: "load-earlier" }
|
|
207
|
+
| { type: "proposal-accept"; messageId: string; proposalId: string }
|
|
208
|
+
| { type: "proposal-reject"; messageId: string; proposalId: string }
|
|
209
|
+
| { type: "jump-to-latest"; unseen: number };
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Map it onto fancy-auto-common's `AutoActivityEvent` in the host, as with
|
|
213
|
+
`JsonEditor`'s `onActivity`.
|
|
214
|
+
- **Trust but verify.** Proposals are the staged-write affordance: the agent
|
|
215
|
+
proposes, the human decides.
|
|
216
|
+
- **Bridgeable.** A bridge reads and writes the same JSON the host renders
|
|
217
|
+
(`messages`, proposal decisions). The thread itself adds nothing an agent
|
|
218
|
+
has to scrape.
|
|
219
|
+
|
|
220
|
+
## Accessibility
|
|
221
|
+
|
|
222
|
+
- The log is `role="log"`, `aria-live="polite"`, `aria-relevant="additions"`,
|
|
223
|
+
focusable (`tabIndex=0`) so it can be scrolled from the keyboard.
|
|
224
|
+
- It is labelled by `heading` (rendered as `h2`, or `headingLevel`), or by
|
|
225
|
+
`labels.log` ("Conversation") when there is no heading.
|
|
226
|
+
- Every string is in `labels` for translation, including role names and the
|
|
227
|
+
"N new messages" text.
|
|
228
|
+
|
|
229
|
+
## Layouts
|
|
230
|
+
|
|
231
|
+
### Side panel (Charlotte)
|
|
232
|
+
|
|
233
|
+
```tsx
|
|
234
|
+
<ResizablePanels defaultValue={68} label="Resize the chat panel">
|
|
235
|
+
<Editor value={session.state.doc} onOp={session.applyHuman} />
|
|
236
|
+
<ChatThread
|
|
237
|
+
className="h-full"
|
|
238
|
+
heading="Charlotte"
|
|
239
|
+
headingLevel={2}
|
|
240
|
+
messages={session.messages} // reduced from the event log
|
|
241
|
+
streaming="progressive"
|
|
242
|
+
ownRoles={["user"]}
|
|
243
|
+
groupConsecutive
|
|
244
|
+
onProposalAccept={(m) => session.accept(m.proposal!.id)}
|
|
245
|
+
onProposalReject={(m) => session.reject(m.proposal!.id)}
|
|
246
|
+
onActivity={(e) => activity.emit(toAutoActivity(e))}
|
|
247
|
+
renderActions={(m) => m.role === "assistant" && m.status === "sent" ? <CopyButton text={m.content} /> : null}
|
|
248
|
+
composer={
|
|
249
|
+
<PromptInput
|
|
250
|
+
placeholder="Ask Charlotte to change the page..."
|
|
251
|
+
sendDisabled={session.running}
|
|
252
|
+
onSubmit={(text) => session.send(text)}
|
|
253
|
+
/>
|
|
254
|
+
}
|
|
255
|
+
/>
|
|
256
|
+
</ResizablePanels>
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Ticket thread (support)
|
|
260
|
+
|
|
261
|
+
```tsx
|
|
262
|
+
<ChatThread
|
|
263
|
+
className="h-full"
|
|
264
|
+
heading={`${ticket.reference}: ${ticket.subject}`}
|
|
265
|
+
messages={ticket.messages} // the server already filtered internal notes for customers
|
|
266
|
+
ownRoles={["staff"]}
|
|
267
|
+
separators="day"
|
|
268
|
+
hasEarlier={ticket.hasEarlier}
|
|
269
|
+
onLoadEarlier={() => loadEarlier(ticket.reference)}
|
|
270
|
+
onProposalAccept={(m) => cards.approve(m.proposal!.id, m.meta?.version as number)}
|
|
271
|
+
onProposalReject={(m) => cards.openReject(m.proposal!.id)}
|
|
272
|
+
onActionError={(err) => toast.error(String(err))}
|
|
273
|
+
composer={
|
|
274
|
+
<Composer
|
|
275
|
+
actions={<MultiSwitch value={mode} onValueChange={setMode} list={[{ value: "public", label: "Reply" }, { value: "internal", label: "Internal note" }]} />}
|
|
276
|
+
onSubmit={(text) => postMessage(ticket.reference, { text, visibility: mode })}
|
|
277
|
+
/>
|
|
278
|
+
}
|
|
279
|
+
/>
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Props
|
|
283
|
+
|
|
284
|
+
| Prop | Type | Default | Description |
|
|
285
|
+
|------|------|---------|-------------|
|
|
286
|
+
| messages | `ChatMessageData[]` | - | The messages, oldest first. Required |
|
|
287
|
+
| heading | `ReactNode` | - | Visible title; the log is labelled by it |
|
|
288
|
+
| headingLevel | `2 \| 3 \| 4 \| 5 \| 6` | `2` | Heading element |
|
|
289
|
+
| composer | `ReactNode` | - | Rendered below the log (`Composer` / `PromptInput`) |
|
|
290
|
+
| renderActions | `(m) => ReactNode` | - | Per-message actions slot |
|
|
291
|
+
| onLoadEarlier | `() => void \| Promise` | - | Shows the load-earlier control |
|
|
292
|
+
| hasEarlier | `boolean` | `true` with `onLoadEarlier` | Hide the control when there is nothing earlier |
|
|
293
|
+
| loadingEarlier | `boolean` | `false` | Host-driven busy state for the control |
|
|
294
|
+
| onProposalAccept / onProposalReject | `(m) => void \| Promise` | - | Proposal decisions |
|
|
295
|
+
| onActionError | `(error, action) => void` | - | A returned Promise rejected |
|
|
296
|
+
| onActivity | `(e: ChatThreadActivity) => void` | - | Every interaction |
|
|
297
|
+
| stickToBottom | `boolean` | `true` | Follow new messages while at the bottom |
|
|
298
|
+
| bottomThreshold | `number` | `48` | Pixels from the bottom that count as "at the bottom" |
|
|
299
|
+
| onAtBottomChange | `(atBottom) => void` | - | The reader arrived at / left the bottom |
|
|
300
|
+
| separators | `"none" \| "day"` | `"none"` | Day separators |
|
|
301
|
+
| formatDay / formatTime | `(d: Date) => string` | locale | Date and time labels |
|
|
302
|
+
| groupConsecutive | `boolean \| number` | `false` | Merge consecutive messages (5 min, or ms) |
|
|
303
|
+
| ownRoles | `string[]` | `["user"]` | Roles aligned as the viewer's own |
|
|
304
|
+
| streaming | `true \| "progressive"` | `true` | How a streaming message renders its tail |
|
|
305
|
+
| contentOptions | `ChatContentOptions` | - | Passed to every `ContentRenderer` (no `unsafe`) |
|
|
306
|
+
| renderAttachment | `(a, m) => ReactNode` | - | Replace one attachment's rendering |
|
|
307
|
+
| empty | `ReactNode` | - | Shown when there are no messages |
|
|
308
|
+
| labels | `Partial<ChatThreadLabels>` | English | Every visible string |
|
|
309
|
+
| classNames | `ChatThreadClassNames` | - | `root`, `heading`, `log`, `composer`, `separator`, `jump`, `message`, `bubble`, `header`, `actions`, `proposal` |
|
|
310
|
+
| ref | `Ref<ChatThreadHandle>` | - | `scrollToLatest`, `scrollToMessage`, `element` |
|
|
311
|
+
|
|
312
|
+
Other HTML attributes (including `data-*`) go on the root; `onScroll` is
|
|
313
|
+
forwarded from the log, which is the element that scrolls.
|
|
314
|
+
|
|
315
|
+
`ChatMessage` takes `message`, `actions`, `grouped` and the same display props
|
|
316
|
+
(`ownRoles`, `streaming`, `contentOptions`, `labels`, `formatTime`,
|
|
317
|
+
`renderAttachment`, the proposal callbacks, `onActivity`, `onActionError`,
|
|
318
|
+
`classNames`).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@particle-academy/react-fancy",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.71.0",
|
|
4
4
|
"description": "React UI component library for Human+ UX — controlled, agent-bridgeable primitives",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -173,6 +173,16 @@
|
|
|
173
173
|
"default": "./dist/chat-drawer.cjs"
|
|
174
174
|
}
|
|
175
175
|
},
|
|
176
|
+
"./chat-thread": {
|
|
177
|
+
"import": {
|
|
178
|
+
"types": "./dist/chat-thread.d.ts",
|
|
179
|
+
"default": "./dist/chat-thread.js"
|
|
180
|
+
},
|
|
181
|
+
"require": {
|
|
182
|
+
"types": "./dist/chat-thread.d.cts",
|
|
183
|
+
"default": "./dist/chat-thread.cjs"
|
|
184
|
+
}
|
|
185
|
+
},
|
|
176
186
|
"./checkbox": {
|
|
177
187
|
"import": {
|
|
178
188
|
"types": "./dist/checkbox.d.ts",
|