@duskmoon-dev/components 0.4.1 → 0.5.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.
@@ -1,7 +1,21 @@
1
1
  "use client";
2
2
 
3
3
  // src/components/chat/Chat.tsx
4
- import { forwardRef } from "react";
4
+ import { forwardRef as forwardRef3 } from "react";
5
+
6
+ // src/components/chat/ChatComposer.tsx
7
+ import {
8
+ forwardRef as forwardRef2,
9
+ useEffect,
10
+ useId,
11
+ useLayoutEffect,
12
+ useRef as useRef2,
13
+ useState as useState2
14
+ } from "react";
15
+
16
+ // src/components/button/Button.tsx
17
+ import React from "react";
18
+ import { createPortal } from "react-dom";
5
19
 
6
20
  // ../../node_modules/.bun/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
7
21
  function r(e) {
@@ -28,6 +42,1063 @@ function clsx() {
28
42
  function cn(...inputs) {
29
43
  return clsx(inputs);
30
44
  }
45
+ // src/classes/button.ts
46
+ var buttonBaseClass = "btn";
47
+ var buttonColorClasses = {
48
+ primary: "btn-primary",
49
+ secondary: "btn-secondary",
50
+ tertiary: "btn-tertiary",
51
+ accent: "btn-accent",
52
+ neutral: "btn-neutral",
53
+ base: "btn-base",
54
+ info: "btn-info",
55
+ success: "btn-success",
56
+ warning: "btn-warning",
57
+ error: "btn-error"
58
+ };
59
+ var buttonAppearanceClasses = {
60
+ filled: "",
61
+ outline: "btn-outline",
62
+ tonal: "btn-tonal",
63
+ ghost: "btn-ghost",
64
+ text: "btn-text"
65
+ };
66
+ var buttonShapeClasses = {
67
+ rect: "",
68
+ circle: "btn-circle btn-icon",
69
+ square: "btn-square"
70
+ };
71
+ var buttonSizeClasses = {
72
+ xs: "btn-xs",
73
+ sm: "btn-sm",
74
+ md: "btn-md",
75
+ lg: "btn-lg"
76
+ };
77
+ var buttonBlockClass = "btn-block";
78
+ var buttonIsLoadingClass = "btn-loading";
79
+ function getButtonClasses({
80
+ color = "primary",
81
+ appearance = "filled",
82
+ shape = "rect",
83
+ size = "md",
84
+ block,
85
+ isLoading,
86
+ className
87
+ }) {
88
+ return cn(buttonBaseClass, buttonColorClasses[color], buttonAppearanceClasses[appearance], buttonShapeClasses[shape], buttonSizeClasses[size], shape === "circle" && size === "xs" && "btn-icon-sm", shape === "circle" && size === "sm" && "btn-icon-sm", shape === "circle" && size === "lg" && "btn-icon-lg", block && buttonBlockClass, isLoading && buttonIsLoadingClass, className);
89
+ }
90
+
91
+ // src/components/button/Button.tsx
92
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
93
+ function isPromiseLike(value) {
94
+ return value !== null && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
95
+ }
96
+ function Button({
97
+ color = "primary",
98
+ appearance = "filled",
99
+ shape = "rect",
100
+ size = "md",
101
+ block,
102
+ isLoading,
103
+ leftIcon,
104
+ rightIcon,
105
+ className,
106
+ children,
107
+ disabled,
108
+ ref,
109
+ confirm,
110
+ onClick,
111
+ ...props
112
+ }) {
113
+ const buttonRef = React.useRef(null);
114
+ const popoverRef = React.useRef(null);
115
+ const cancelRef = React.useRef(null);
116
+ const approvalRef = React.useRef("idle");
117
+ const pendingRef = React.useRef(false);
118
+ const actionRef = React.useRef(0);
119
+ const operationRef = React.useRef(0);
120
+ const mountedRef = React.useRef(false);
121
+ const [portalTarget, setPortalTarget] = React.useState(null);
122
+ const [confirmLoading, setConfirmLoading] = React.useState(false);
123
+ const [confirmError, setConfirmError] = React.useState(false);
124
+ const generatedId = React.useId().replace(/:/g, "");
125
+ const popoverId = `button-confirm-${generatedId}`;
126
+ const titleId = `${popoverId}-title`;
127
+ const descriptionId = `${popoverId}-description`;
128
+ const anchor = `--${popoverId}-anchor`;
129
+ const hasConfirm = Boolean(confirm);
130
+ const confirmation = confirm && typeof confirm === "object" ? confirm : undefined;
131
+ const title = confirmation ? confirmation.title : "Confirm action";
132
+ const hasTitle = title !== undefined && title !== null && title !== false && title !== "";
133
+ const content = confirmation?.component ?? confirmation?.message ?? confirmation?.description;
134
+ const hasContent = content !== undefined && content !== null && content !== false && content !== "";
135
+ const mergedRef = React.useCallback((node) => {
136
+ buttonRef.current = node;
137
+ if (!node) {
138
+ if (typeof ref === "function")
139
+ ref(null);
140
+ else if (ref)
141
+ ref.current = null;
142
+ return;
143
+ }
144
+ const cleanup = typeof ref === "function" ? ref(node) : undefined;
145
+ if (ref && typeof ref !== "function")
146
+ ref.current = node;
147
+ return () => {
148
+ buttonRef.current = null;
149
+ if (typeof cleanup === "function")
150
+ cleanup();
151
+ else if (typeof ref === "function")
152
+ ref(null);
153
+ else if (ref)
154
+ ref.current = null;
155
+ };
156
+ }, [ref]);
157
+ React.useEffect(() => {
158
+ mountedRef.current = true;
159
+ return () => {
160
+ mountedRef.current = false;
161
+ operationRef.current += 1;
162
+ };
163
+ }, []);
164
+ React.useEffect(() => {
165
+ operationRef.current += 1;
166
+ approvalRef.current = "idle";
167
+ setConfirmError(false);
168
+ setPortalTarget(hasConfirm ? document.body : null);
169
+ }, [hasConfirm]);
170
+ function closePopover() {
171
+ if (popoverRef.current?.matches(":popover-open")) {
172
+ popoverRef.current.hidePopover();
173
+ }
174
+ buttonRef.current?.focus();
175
+ }
176
+ function handleClick(event) {
177
+ if (!confirm) {
178
+ onClick?.(event);
179
+ return;
180
+ }
181
+ if (approvalRef.current === "native") {
182
+ approvalRef.current = "idle";
183
+ return;
184
+ }
185
+ if (approvalRef.current === "action") {
186
+ approvalRef.current = "idle";
187
+ let result;
188
+ try {
189
+ result = onClick?.(event);
190
+ } catch {
191
+ event.preventDefault();
192
+ pendingRef.current = false;
193
+ setConfirmError(true);
194
+ return;
195
+ }
196
+ if (!isPromiseLike(result)) {
197
+ pendingRef.current = false;
198
+ closePopover();
199
+ return;
200
+ }
201
+ const userPreventedDefault = event.defaultPrevented;
202
+ event.preventDefault();
203
+ setConfirmLoading(true);
204
+ const action = actionRef.current;
205
+ const operation = operationRef.current;
206
+ Promise.resolve(result).then(() => {
207
+ if (!mountedRef.current || action !== actionRef.current)
208
+ return;
209
+ pendingRef.current = false;
210
+ setConfirmLoading(false);
211
+ if (operation !== operationRef.current)
212
+ return;
213
+ closePopover();
214
+ if (!userPreventedDefault && !buttonRef.current?.disabled) {
215
+ approvalRef.current = "native";
216
+ buttonRef.current?.click();
217
+ approvalRef.current = "idle";
218
+ }
219
+ }, () => {
220
+ if (!mountedRef.current || action !== actionRef.current)
221
+ return;
222
+ pendingRef.current = false;
223
+ setConfirmLoading(false);
224
+ if (operation !== operationRef.current)
225
+ return;
226
+ setConfirmError(true);
227
+ cancelRef.current?.focus();
228
+ });
229
+ return;
230
+ }
231
+ event.preventDefault();
232
+ if (disabled || isLoading || pendingRef.current)
233
+ return;
234
+ if (popoverRef.current?.matches(":popover-open")) {
235
+ closePopover();
236
+ return;
237
+ }
238
+ setConfirmError(false);
239
+ popoverRef.current?.showPopover();
240
+ cancelRef.current?.focus();
241
+ }
242
+ function handleConfirm(event) {
243
+ event.preventDefault();
244
+ event.stopPropagation();
245
+ const trigger = buttonRef.current;
246
+ if (disabled || isLoading || pendingRef.current || !trigger || trigger.disabled || !event.currentTarget.matches(":popover-open"))
247
+ return;
248
+ setConfirmError(false);
249
+ pendingRef.current = true;
250
+ actionRef.current += 1;
251
+ approvalRef.current = "action";
252
+ trigger.click();
253
+ if (approvalRef.current === "action") {
254
+ approvalRef.current = "idle";
255
+ pendingRef.current = false;
256
+ }
257
+ }
258
+ const classes = getButtonClasses({
259
+ color,
260
+ appearance,
261
+ shape,
262
+ size,
263
+ block,
264
+ isLoading,
265
+ className
266
+ });
267
+ const confirmClasses = getButtonClasses({
268
+ color,
269
+ size: "sm",
270
+ isLoading: confirmLoading
271
+ });
272
+ return /* @__PURE__ */ jsxs(Fragment, {
273
+ children: [
274
+ /* @__PURE__ */ jsxs("button", {
275
+ ref: confirm ? mergedRef : ref,
276
+ className: classes,
277
+ disabled: disabled || isLoading,
278
+ "aria-busy": isLoading,
279
+ "aria-disabled": disabled || isLoading,
280
+ ...props,
281
+ style: confirm ? { ...props.style, anchorName: anchor } : props.style,
282
+ "aria-haspopup": confirm ? "dialog" : props["aria-haspopup"],
283
+ "aria-controls": confirm ? popoverId : props["aria-controls"],
284
+ onClick: confirm ? handleClick : onClick,
285
+ children: [
286
+ isLoading && /* @__PURE__ */ jsx("span", {
287
+ className: "btn-spinner"
288
+ }),
289
+ !isLoading && leftIcon && /* @__PURE__ */ jsx("span", {
290
+ className: "btn-icon-left",
291
+ children: leftIcon
292
+ }),
293
+ /* @__PURE__ */ jsx("span", {
294
+ className: "btn-content",
295
+ children
296
+ }),
297
+ !isLoading && rightIcon && /* @__PURE__ */ jsx("span", {
298
+ className: "btn-icon-right",
299
+ children: rightIcon
300
+ })
301
+ ]
302
+ }),
303
+ confirm && portalTarget && createPortal(/* @__PURE__ */ jsxs("form", {
304
+ id: popoverId,
305
+ ref: popoverRef,
306
+ popover: "auto",
307
+ role: "dialog",
308
+ "aria-labelledby": hasTitle ? titleId : undefined,
309
+ "aria-label": hasTitle ? undefined : "Confirm action",
310
+ "aria-describedby": hasContent ? descriptionId : undefined,
311
+ className: "popover popover-confirm popover-bottom",
312
+ style: { positionAnchor: anchor },
313
+ onSubmit: handleConfirm,
314
+ onClick: (event) => event.stopPropagation(),
315
+ onToggle: (event) => {
316
+ if (!event.currentTarget.matches(":popover-open")) {
317
+ operationRef.current += 1;
318
+ if (event.currentTarget.contains(document.activeElement) || document.activeElement === document.body) {
319
+ buttonRef.current?.focus();
320
+ }
321
+ }
322
+ },
323
+ children: [
324
+ hasTitle && /* @__PURE__ */ jsx("div", {
325
+ className: "popover-header",
326
+ children: /* @__PURE__ */ jsx("h3", {
327
+ id: titleId,
328
+ className: "popover-title",
329
+ children: title
330
+ })
331
+ }),
332
+ hasContent && /* @__PURE__ */ jsx("div", {
333
+ id: descriptionId,
334
+ className: "popover-body",
335
+ children: content
336
+ }),
337
+ confirmError && /* @__PURE__ */ jsx("div", {
338
+ role: "alert",
339
+ className: "popover-body",
340
+ children: "The action failed. Please try again."
341
+ }),
342
+ /* @__PURE__ */ jsxs("div", {
343
+ className: "popover-footer",
344
+ children: [
345
+ /* @__PURE__ */ jsx("button", {
346
+ ref: cancelRef,
347
+ type: "button",
348
+ className: "btn btn-text btn-sm",
349
+ popoverTarget: popoverId,
350
+ popoverTargetAction: "hide",
351
+ disabled: confirmLoading,
352
+ children: confirmation?.cancelText ?? "Cancel"
353
+ }),
354
+ /* @__PURE__ */ jsx("button", {
355
+ type: "submit",
356
+ className: confirmClasses,
357
+ disabled: disabled || isLoading || confirmLoading,
358
+ "aria-busy": confirmLoading,
359
+ children: confirmation?.confirmText ?? "Confirm"
360
+ })
361
+ ]
362
+ })
363
+ ]
364
+ }), portalTarget)
365
+ ]
366
+ });
367
+ }
368
+ // src/components/input/Input.tsx
369
+ import {
370
+ forwardRef,
371
+ useImperativeHandle,
372
+ useRef,
373
+ useState
374
+ } from "react";
375
+
376
+ // src/classes/input.ts
377
+ var inputBaseClass = "input";
378
+ var inputWrapperClass = "input-wrapper";
379
+ var inputAffixWrapperClass = "input-affix-wrapper";
380
+ var inputPrefixClass = "input-prefix";
381
+ var inputSuffixClass = "input-suffix";
382
+ var inputClearClass = "input-clear";
383
+ var inputSearchClass = "input-search";
384
+ var inputSearchButtonClass = "input-search-button";
385
+ var inputPasswordClass = "input-password";
386
+ var inputPasswordToggleClass = "input-password-toggle";
387
+ var inputGroupClass = "input-group";
388
+ var textAreaBaseClass = "textarea";
389
+ var textAreaWrapperClass = "textarea-wrapper";
390
+ var textAreaCountClass = "textarea-counter";
391
+ var textAreaCountExceededClass = "textarea-counter-exceeded";
392
+ var textAreaAutoSizeClass = "textarea-auto-resize";
393
+ var inputVariantClasses = {
394
+ filled: "input-filled",
395
+ outlined: "input-outlined"
396
+ };
397
+ var inputSizeClasses = {
398
+ sm: "input-sm",
399
+ md: "input-md",
400
+ lg: "input-lg"
401
+ };
402
+ var inputStatusClasses = {
403
+ error: "input-error",
404
+ success: "input-success"
405
+ };
406
+ var textAreaVariantClasses = {
407
+ filled: "textarea-filled",
408
+ outlined: "textarea-outlined"
409
+ };
410
+ var textAreaSizeClasses = {
411
+ sm: "textarea-sm",
412
+ md: "",
413
+ lg: "textarea-lg"
414
+ };
415
+ var textAreaStatusClasses = {
416
+ error: "textarea-error",
417
+ success: "textarea-success"
418
+ };
419
+ var textAreaResizeClasses = {
420
+ none: "textarea-resize-none",
421
+ both: "textarea-resize-both",
422
+ horizontal: "textarea-resize-horizontal",
423
+ vertical: "textarea-resize-vertical"
424
+ };
425
+ function getInputClasses({
426
+ size = "md",
427
+ status,
428
+ variant = "outlined",
429
+ className
430
+ }) {
431
+ return cn(inputBaseClass, inputVariantClasses[variant], inputSizeClasses[size], status && inputStatusClasses[status], className);
432
+ }
433
+ function getInputWrapperClasses({
434
+ affixed,
435
+ className
436
+ }) {
437
+ return cn(inputWrapperClass, affixed && inputAffixWrapperClass, className);
438
+ }
439
+ function getSearchWrapperClasses({ className }) {
440
+ return cn(inputSearchClass, className);
441
+ }
442
+ function getPasswordWrapperClasses({
443
+ className
444
+ }) {
445
+ return cn(inputPasswordClass, className);
446
+ }
447
+ function getTextAreaClasses({
448
+ size = "md",
449
+ status,
450
+ variant = "outlined",
451
+ resize = "vertical",
452
+ autoSize,
453
+ className
454
+ }) {
455
+ return cn(textAreaBaseClass, textAreaVariantClasses[variant], textAreaSizeClasses[size], status && textAreaStatusClasses[status], textAreaResizeClasses[resize], autoSize && textAreaAutoSizeClass, className);
456
+ }
457
+ function getTextAreaCountClasses({ exceeded }) {
458
+ return cn(textAreaCountClass, exceeded && textAreaCountExceededClass);
459
+ }
460
+
461
+ // src/components/input/Input.tsx
462
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
463
+ function toTextValue(value) {
464
+ if (Array.isArray(value)) {
465
+ return value.join(",");
466
+ }
467
+ return value == null ? "" : String(value);
468
+ }
469
+ var InputRoot = forwardRef(({
470
+ allowClear,
471
+ className,
472
+ defaultValue,
473
+ disabled,
474
+ onChange,
475
+ prefix,
476
+ size = "md",
477
+ status,
478
+ suffix,
479
+ type = "text",
480
+ value,
481
+ variant = "outlined",
482
+ ...props
483
+ }, ref) => {
484
+ const inputRef = useRef(null);
485
+ const [internalValue, setInternalValue] = useState(() => toTextValue(defaultValue));
486
+ const isControlled = value !== undefined;
487
+ const currentValue = isControlled ? toTextValue(value) : internalValue;
488
+ const hasAffix = prefix != null || suffix != null || allowClear;
489
+ useImperativeHandle(ref, () => inputRef.current);
490
+ function handleChange(event) {
491
+ if (!isControlled) {
492
+ setInternalValue(event.currentTarget.value);
493
+ }
494
+ onChange?.(event);
495
+ }
496
+ function handleClear() {
497
+ const input = inputRef.current;
498
+ if (!input || disabled) {
499
+ return;
500
+ }
501
+ input.value = "";
502
+ if (!isControlled) {
503
+ setInternalValue("");
504
+ }
505
+ onChange?.({
506
+ currentTarget: input,
507
+ target: input,
508
+ type: "change"
509
+ });
510
+ input.focus();
511
+ }
512
+ const control = /* @__PURE__ */ jsx2("input", {
513
+ ...props,
514
+ ref: inputRef,
515
+ type,
516
+ className: getInputClasses({ size, status, variant, className }),
517
+ disabled,
518
+ value: currentValue,
519
+ onChange: handleChange
520
+ });
521
+ if (!hasAffix) {
522
+ return control;
523
+ }
524
+ const showClear = allowClear && !disabled && currentValue.length > 0;
525
+ return /* @__PURE__ */ jsxs2("span", {
526
+ className: getInputWrapperClasses({ affixed: hasAffix }),
527
+ children: [
528
+ prefix != null ? /* @__PURE__ */ jsx2("span", {
529
+ className: inputPrefixClass,
530
+ children: prefix
531
+ }) : null,
532
+ control,
533
+ showClear ? /* @__PURE__ */ jsx2("button", {
534
+ type: "button",
535
+ className: inputClearClass,
536
+ "aria-label": "Clear input",
537
+ onClick: handleClear,
538
+ children: "x"
539
+ }) : null,
540
+ suffix != null ? /* @__PURE__ */ jsx2("span", {
541
+ className: inputSuffixClass,
542
+ children: suffix
543
+ }) : null
544
+ ]
545
+ });
546
+ });
547
+ InputRoot.displayName = "Input";
548
+ var Search = forwardRef(({
549
+ className,
550
+ disabled,
551
+ enterButton,
552
+ loading,
553
+ onKeyDown,
554
+ onSearch,
555
+ ...props
556
+ }, ref) => {
557
+ const inputRef = useRef(null);
558
+ useImperativeHandle(ref, () => inputRef.current);
559
+ function emitSearch(event) {
560
+ onSearch?.(inputRef.current?.value ?? "", event);
561
+ }
562
+ function handleKeyDown(event) {
563
+ onKeyDown?.(event);
564
+ if (!event.defaultPrevented && event.key === "Enter") {
565
+ emitSearch(event);
566
+ }
567
+ }
568
+ const buttonContent = enterButton === true ? "Search" : enterButton ?? "Search";
569
+ return /* @__PURE__ */ jsxs2("span", {
570
+ className: getSearchWrapperClasses({ className }),
571
+ children: [
572
+ /* @__PURE__ */ jsx2(InputRoot, {
573
+ ...props,
574
+ ref: inputRef,
575
+ disabled,
576
+ onKeyDown: handleKeyDown
577
+ }),
578
+ enterButton || loading ? /* @__PURE__ */ jsx2("button", {
579
+ type: "button",
580
+ className: inputSearchButtonClass,
581
+ disabled: disabled || loading,
582
+ "aria-busy": loading || undefined,
583
+ onClick: emitSearch,
584
+ children: loading ? "Loading" : buttonContent
585
+ }) : null
586
+ ]
587
+ });
588
+ });
589
+ Search.displayName = "Input.Search";
590
+ var Password = forwardRef(({ className, disabled, visibilityToggle = true, ...props }, ref) => {
591
+ const [visible, setVisible] = useState(false);
592
+ const type = visible ? "text" : "password";
593
+ const toggle = visibilityToggle ? /* @__PURE__ */ jsx2("button", {
594
+ type: "button",
595
+ className: inputPasswordToggleClass,
596
+ disabled,
597
+ "aria-label": visible ? "Hide password" : "Show password",
598
+ onClick: () => {
599
+ setVisible((current) => !current);
600
+ },
601
+ children: visible ? "Hide" : "Show"
602
+ }) : undefined;
603
+ return /* @__PURE__ */ jsx2("span", {
604
+ className: getPasswordWrapperClasses({ className }),
605
+ children: /* @__PURE__ */ jsx2(InputRoot, {
606
+ ...props,
607
+ ref,
608
+ disabled,
609
+ type,
610
+ suffix: toggle
611
+ })
612
+ });
613
+ });
614
+ Password.displayName = "Input.Password";
615
+ var TextArea = forwardRef(({
616
+ autoSize,
617
+ className,
618
+ defaultValue,
619
+ maxLength,
620
+ onChange,
621
+ resize = "vertical",
622
+ showCount,
623
+ size = "md",
624
+ status,
625
+ value,
626
+ variant = "outlined",
627
+ ...props
628
+ }, ref) => {
629
+ const [internalValue, setInternalValue] = useState(() => toTextValue(defaultValue));
630
+ const isControlled = value !== undefined;
631
+ const currentValue = isControlled ? toTextValue(value) : internalValue;
632
+ const count = currentValue.length;
633
+ const exceeded = maxLength !== undefined && count > maxLength;
634
+ function handleChange(event) {
635
+ if (!isControlled) {
636
+ setInternalValue(event.currentTarget.value);
637
+ }
638
+ onChange?.(event);
639
+ }
640
+ const control = /* @__PURE__ */ jsx2("textarea", {
641
+ ...props,
642
+ ref,
643
+ className: getTextAreaClasses({
644
+ size,
645
+ status,
646
+ variant,
647
+ resize,
648
+ autoSize: Boolean(autoSize),
649
+ className
650
+ }),
651
+ maxLength,
652
+ value: currentValue,
653
+ onChange: handleChange
654
+ });
655
+ if (!showCount) {
656
+ return control;
657
+ }
658
+ return /* @__PURE__ */ jsxs2("span", {
659
+ className: textAreaWrapperClass,
660
+ children: [
661
+ control,
662
+ /* @__PURE__ */ jsxs2("span", {
663
+ className: getTextAreaCountClasses({ exceeded }),
664
+ children: [
665
+ count,
666
+ maxLength !== undefined ? ` / ${maxLength}` : null
667
+ ]
668
+ })
669
+ ]
670
+ });
671
+ });
672
+ TextArea.displayName = "Input.TextArea";
673
+ var Group = forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsx2("div", {
674
+ ...props,
675
+ ref,
676
+ className: cn(inputGroupClass, className),
677
+ children
678
+ }));
679
+ Group.displayName = "Input.Group";
680
+ var Input = Object.assign(InputRoot, {
681
+ Search,
682
+ Password,
683
+ TextArea,
684
+ Group
685
+ });
686
+ // src/components/chat/ChatComposer.tsx
687
+ import { jsx as jsx3, jsxs as jsxs3, Fragment as Fragment2 } from "react/jsx-runtime";
688
+ function AttachmentPreview({ file }) {
689
+ const imageRef = useRef2(null);
690
+ useEffect(() => {
691
+ const image = imageRef.current;
692
+ if (!image || !file.type.startsWith("image/") || !URL.createObjectURL)
693
+ return;
694
+ const objectUrl = URL.createObjectURL(file);
695
+ image.src = objectUrl;
696
+ image.hidden = false;
697
+ return () => {
698
+ image.hidden = true;
699
+ image.removeAttribute("src");
700
+ URL.revokeObjectURL(objectUrl);
701
+ };
702
+ }, [file]);
703
+ return file.type.startsWith("image/") ? /* @__PURE__ */ jsx3("img", {
704
+ ref: imageRef,
705
+ alt: file.name,
706
+ hidden: true
707
+ }) : null;
708
+ }
709
+ function AttachmentContent({
710
+ file,
711
+ renderAttachment,
712
+ context
713
+ }) {
714
+ return renderAttachment(file, context);
715
+ }
716
+ function ExtraActions({
717
+ children,
718
+ context
719
+ }) {
720
+ return typeof children === "function" ? children(context) : children;
721
+ }
722
+ var ChatComposer = forwardRef2(({
723
+ value,
724
+ onValueChange,
725
+ onSubmit,
726
+ attachments = [],
727
+ onAttachmentsChange,
728
+ accept,
729
+ multiple = true,
730
+ allowAttachmentOnly = false,
731
+ disabled = false,
732
+ busy = false,
733
+ generating = false,
734
+ canSubmit = true,
735
+ onStop,
736
+ error,
737
+ onSubmitError,
738
+ clearOnSubmit = true,
739
+ completion,
740
+ minRows = 1,
741
+ maxRows = 6,
742
+ textareaProps,
743
+ attachLabel = "Attach files",
744
+ sendLabel = "Send",
745
+ stopLabel = "Stop",
746
+ renderAttachment,
747
+ children,
748
+ className,
749
+ ...props
750
+ }, ref) => {
751
+ const id = useId();
752
+ const editorRef = useRef2(null);
753
+ const pickerRef = useRef2(null);
754
+ const mountedRef = useRef2(false);
755
+ const composingRef = useRef2(false);
756
+ const submittingRef = useRef2(false);
757
+ const revisionRef = useRef2(0);
758
+ const observedValueRef = useRef2(value);
759
+ const entriesRef = useRef2([]);
760
+ const nextEntryRef = useRef2(0);
761
+ const [submitting, setSubmitting] = useState2(false);
762
+ const [submitError, setSubmitError] = useState2();
763
+ const [activeId, setActiveId] = useState2();
764
+ const [dismissed, setDismissed] = useState2(false);
765
+ const latestRef = useRef2({
766
+ value,
767
+ onValueChange,
768
+ attachments,
769
+ onAttachmentsChange,
770
+ onSubmitError,
771
+ clearOnSubmit
772
+ });
773
+ function reconcileEntries(files) {
774
+ const previous = [...entriesRef.current];
775
+ entriesRef.current = files.map((file) => {
776
+ const index = previous.findIndex((entry) => entry.file === file);
777
+ if (index < 0)
778
+ return { file, id: nextEntryRef.current++ };
779
+ return previous.splice(index, 1)[0];
780
+ });
781
+ }
782
+ useLayoutEffect(() => {
783
+ mountedRef.current = true;
784
+ return () => {
785
+ mountedRef.current = false;
786
+ };
787
+ }, []);
788
+ useLayoutEffect(() => {
789
+ if (observedValueRef.current !== value) {
790
+ revisionRef.current++;
791
+ observedValueRef.current = value;
792
+ }
793
+ reconcileEntries(attachments);
794
+ latestRef.current = {
795
+ value,
796
+ onValueChange,
797
+ attachments,
798
+ onAttachmentsChange,
799
+ onSubmitError,
800
+ clearOnSubmit
801
+ };
802
+ });
803
+ const minimumRows = Math.max(1, Math.floor(minRows));
804
+ const maximumRows = Math.max(minimumRows, Math.floor(maxRows));
805
+ useLayoutEffect(() => {
806
+ const editor = editorRef.current;
807
+ if (!editor)
808
+ return;
809
+ function measure() {
810
+ if (!editor)
811
+ return;
812
+ const style = getComputedStyle(editor);
813
+ const lineHeight = parseFloat(style.lineHeight) || 24;
814
+ const padding = (parseFloat(style.paddingTop) || 0) + (parseFloat(style.paddingBottom) || 0);
815
+ const borders = (parseFloat(style.borderTopWidth) || 0) + (parseFloat(style.borderBottomWidth) || 0);
816
+ const adjustment = style.boxSizing === "border-box" ? padding + borders : 0;
817
+ editor.style.height = "auto";
818
+ const minimum = minimumRows * lineHeight + adjustment;
819
+ const maximum = maximumRows * lineHeight + adjustment;
820
+ const content = editor.scrollHeight - padding + adjustment;
821
+ editor.style.height = `${Math.min(maximum, Math.max(minimum, content))}px`;
822
+ editor.style.overflowY = content > maximum ? "auto" : "hidden";
823
+ }
824
+ measure();
825
+ const observer = typeof ResizeObserver === "undefined" ? undefined : new ResizeObserver(measure);
826
+ observer?.observe(editor);
827
+ window.addEventListener("resize", measure);
828
+ return () => {
829
+ observer?.disconnect();
830
+ window.removeEventListener("resize", measure);
831
+ };
832
+ }, [value, minimumRows, maximumRows, textareaProps?.style]);
833
+ const eligible = canSubmit && !disabled && !busy && !submitting && (value.trim().length > 0 || allowAttachmentOnly && attachments.length > 0);
834
+ const candidates = completion?.items ?? [];
835
+ const enabledCandidates = candidates.filter((item) => !item.disabled);
836
+ const active = enabledCandidates.find((item) => item.id === activeId) ?? enabledCandidates[0];
837
+ const showCompletion = !disabled && !dismissed && candidates.length > 0;
838
+ const visibleError = error ?? submitError;
839
+ const hasError = visibleError !== undefined && visibleError !== null && visibleError !== false;
840
+ function openAttachments() {
841
+ if (!disabled && onAttachmentsChange)
842
+ pickerRef.current?.click();
843
+ }
844
+ function changeAttachments(files) {
845
+ reconcileEntries(files);
846
+ latestRef.current.attachments = files;
847
+ onAttachmentsChange?.(files);
848
+ }
849
+ function removeAttachment(index) {
850
+ if (disabled || !onAttachmentsChange)
851
+ return;
852
+ entriesRef.current.splice(index, 1);
853
+ changeAttachments(attachments.filter((_, fileIndex) => fileIndex !== index));
854
+ }
855
+ function selectCompletion(item) {
856
+ if (!completion || item.disabled || disabled)
857
+ return;
858
+ const editor = editorRef.current;
859
+ completion.onSelect(item, {
860
+ value,
861
+ selectionStart: editor?.selectionStart ?? value.length,
862
+ selectionEnd: editor?.selectionEnd ?? value.length
863
+ });
864
+ setDismissed(true);
865
+ editor?.focus();
866
+ }
867
+ async function submit() {
868
+ if (!eligible || submittingRef.current)
869
+ return;
870
+ submittingRef.current = true;
871
+ setSubmitting(true);
872
+ setSubmitError(undefined);
873
+ const snapshot = { value, files: [...attachments] };
874
+ const submittedRevision = revisionRef.current;
875
+ const submittedEntries = new Set(entriesRef.current.map((entry) => entry.id));
876
+ try {
877
+ const accepted = await onSubmit(snapshot);
878
+ if (!mountedRef.current || accepted === false)
879
+ return;
880
+ const current = latestRef.current;
881
+ if (current.clearOnSubmit) {
882
+ if (revisionRef.current === submittedRevision)
883
+ current.onValueChange("");
884
+ const retained = entriesRef.current.filter((entry) => !submittedEntries.has(entry.id));
885
+ if (retained.length !== entriesRef.current.length) {
886
+ entriesRef.current = retained;
887
+ current.onAttachmentsChange?.(retained.map((entry) => entry.file));
888
+ }
889
+ }
890
+ } catch (cause) {
891
+ if (!mountedRef.current)
892
+ return;
893
+ setSubmitError("Unable to send message. Please try again.");
894
+ latestRef.current.onSubmitError?.(cause);
895
+ } finally {
896
+ submittingRef.current = false;
897
+ if (mountedRef.current)
898
+ setSubmitting(false);
899
+ }
900
+ }
901
+ return /* @__PURE__ */ jsxs3("div", {
902
+ ...props,
903
+ ref,
904
+ className: cn("chat-composer", className),
905
+ children: [
906
+ attachments.length > 0 ? /* @__PURE__ */ jsx3("ul", {
907
+ className: "chat-composer-attachments",
908
+ "aria-label": "Attachments",
909
+ children: attachments.map((file, index) => /* @__PURE__ */ jsx3("li", {
910
+ className: "chat-composer-attachment",
911
+ children: renderAttachment ? /* @__PURE__ */ jsx3(AttachmentContent, {
912
+ file,
913
+ renderAttachment,
914
+ context: {
915
+ remove: () => removeAttachment(index),
916
+ disabled: disabled || !onAttachmentsChange
917
+ }
918
+ }) : /* @__PURE__ */ jsxs3(Fragment2, {
919
+ children: [
920
+ /* @__PURE__ */ jsx3(AttachmentPreview, {
921
+ file
922
+ }),
923
+ /* @__PURE__ */ jsx3("span", {
924
+ className: "chat-composer-filename",
925
+ children: file.name
926
+ }),
927
+ onAttachmentsChange ? /* @__PURE__ */ jsx3(Button, {
928
+ type: "button",
929
+ size: "sm",
930
+ color: "base",
931
+ appearance: "ghost",
932
+ disabled,
933
+ "aria-label": `Remove ${file.name}`,
934
+ onClick: () => removeAttachment(index),
935
+ children: "Remove"
936
+ }) : null
937
+ ]
938
+ })
939
+ }, index))
940
+ }) : null,
941
+ /* @__PURE__ */ jsx3(Input.TextArea, {
942
+ ...textareaProps,
943
+ ref: editorRef,
944
+ className: cn("chat-composer-editor", textareaProps?.className),
945
+ value,
946
+ disabled,
947
+ rows: minimumRows,
948
+ resize: "none",
949
+ "aria-label": textareaProps?.["aria-label"] ?? (textareaProps?.["aria-labelledby"] ? undefined : "Message"),
950
+ "aria-describedby": cn(textareaProps?.["aria-describedby"], hasError && `${id}-error`) || undefined,
951
+ "aria-invalid": hasError ? true : textareaProps?.["aria-invalid"],
952
+ "aria-controls": showCompletion ? `${id}-completions` : undefined,
953
+ "aria-autocomplete": completion ? "list" : undefined,
954
+ "aria-haspopup": completion ? "listbox" : undefined,
955
+ "aria-activedescendant": showCompletion && active ? `${id}-option-${candidates.indexOf(active)}` : undefined,
956
+ onChange: (event) => {
957
+ revisionRef.current++;
958
+ observedValueRef.current = event.currentTarget.value;
959
+ setSubmitError(undefined);
960
+ setDismissed(false);
961
+ setActiveId(undefined);
962
+ onValueChange(event.currentTarget.value);
963
+ },
964
+ onCompositionStart: (event) => {
965
+ composingRef.current = true;
966
+ textareaProps?.onCompositionStart?.(event);
967
+ },
968
+ onCompositionEnd: (event) => {
969
+ composingRef.current = false;
970
+ textareaProps?.onCompositionEnd?.(event);
971
+ },
972
+ onKeyDown: (event) => {
973
+ textareaProps?.onKeyDown?.(event);
974
+ if (event.defaultPrevented || composingRef.current || event.nativeEvent.isComposing || event.keyCode === 229)
975
+ return;
976
+ if (showCompletion) {
977
+ if (event.key === "Escape") {
978
+ event.preventDefault();
979
+ setDismissed(true);
980
+ return;
981
+ }
982
+ if (enabledCandidates.length > 0 && (event.key === "ArrowDown" || event.key === "ArrowUp")) {
983
+ event.preventDefault();
984
+ const position = enabledCandidates.findIndex((item) => item.id === active?.id);
985
+ const direction = event.key === "ArrowDown" ? 1 : -1;
986
+ setActiveId(enabledCandidates[(position + direction + enabledCandidates.length) % enabledCandidates.length].id);
987
+ return;
988
+ }
989
+ if (active && !event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey && (event.key === "Enter" || event.key === "Tab")) {
990
+ event.preventDefault();
991
+ selectCompletion(active);
992
+ return;
993
+ }
994
+ }
995
+ if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
996
+ event.preventDefault();
997
+ submit();
998
+ }
999
+ }
1000
+ }),
1001
+ showCompletion ? /* @__PURE__ */ jsx3("ul", {
1002
+ id: `${id}-completions`,
1003
+ role: "listbox",
1004
+ "aria-label": completion?.label ?? "Suggestions",
1005
+ className: "chat-composer-completions",
1006
+ children: candidates.map((item, index) => /* @__PURE__ */ jsxs3("li", {
1007
+ id: `${id}-option-${index}`,
1008
+ role: "option",
1009
+ "aria-selected": item.id === active?.id,
1010
+ "aria-disabled": item.disabled || undefined,
1011
+ onMouseDown: (event) => event.preventDefault(),
1012
+ onClick: () => selectCompletion(item),
1013
+ onKeyDown: (event) => {
1014
+ if (event.key === "Enter" || event.key === " ") {
1015
+ event.preventDefault();
1016
+ selectCompletion(item);
1017
+ }
1018
+ },
1019
+ children: [
1020
+ /* @__PURE__ */ jsx3("span", {
1021
+ children: item.label
1022
+ }),
1023
+ item.description ? /* @__PURE__ */ jsx3("span", {
1024
+ className: "chat-composer-description",
1025
+ children: item.description
1026
+ }) : null
1027
+ ]
1028
+ }, item.id))
1029
+ }) : null,
1030
+ hasError ? /* @__PURE__ */ jsx3("div", {
1031
+ id: `${id}-error`,
1032
+ role: "alert",
1033
+ className: "chat-composer-error",
1034
+ children: visibleError
1035
+ }) : null,
1036
+ /* @__PURE__ */ jsxs3("div", {
1037
+ className: "chat-composer-actions",
1038
+ children: [
1039
+ onAttachmentsChange ? /* @__PURE__ */ jsxs3(Fragment2, {
1040
+ children: [
1041
+ /* @__PURE__ */ jsx3("input", {
1042
+ ref: pickerRef,
1043
+ type: "file",
1044
+ accept,
1045
+ multiple,
1046
+ disabled,
1047
+ hidden: true,
1048
+ "aria-label": attachLabel,
1049
+ onChange: (event) => {
1050
+ const files = Array.from(event.currentTarget.files ?? []);
1051
+ event.currentTarget.value = "";
1052
+ if (files.length > 0)
1053
+ changeAttachments([...attachments, ...files]);
1054
+ }
1055
+ }),
1056
+ /* @__PURE__ */ jsx3(Button, {
1057
+ type: "button",
1058
+ color: "base",
1059
+ appearance: "ghost",
1060
+ disabled,
1061
+ onClick: openAttachments,
1062
+ children: attachLabel
1063
+ })
1064
+ ]
1065
+ }) : null,
1066
+ /* @__PURE__ */ jsx3(ExtraActions, {
1067
+ context: {
1068
+ submit: () => {
1069
+ submit();
1070
+ },
1071
+ submitting,
1072
+ canSubmit: eligible,
1073
+ disabled,
1074
+ generating,
1075
+ openAttachments
1076
+ },
1077
+ children
1078
+ }),
1079
+ generating && onStop ? /* @__PURE__ */ jsx3(Button, {
1080
+ type: "button",
1081
+ disabled,
1082
+ onClick: onStop,
1083
+ children: stopLabel
1084
+ }) : null,
1085
+ /* @__PURE__ */ jsx3(Button, {
1086
+ type: "button",
1087
+ color: "primary",
1088
+ disabled: !eligible,
1089
+ "aria-busy": busy || submitting || undefined,
1090
+ onClick: () => {
1091
+ submit();
1092
+ },
1093
+ children: sendLabel
1094
+ })
1095
+ ]
1096
+ })
1097
+ ]
1098
+ });
1099
+ });
1100
+ ChatComposer.displayName = "Chat.Composer";
1101
+
31
1102
  // src/classes/chat.ts
32
1103
  var chatBaseClass = "chat";
33
1104
  var chatAvatarClass = "chat-avatar";
@@ -103,20 +1174,20 @@ function getChatToolClasses({
103
1174
  }
104
1175
 
105
1176
  // src/components/chat/Chat.tsx
106
- import { jsx, jsxs } from "react/jsx-runtime";
107
- var ChatAvatar = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", {
1177
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1178
+ var ChatAvatar = forwardRef3(({ className, ...props }, ref) => /* @__PURE__ */ jsx4("div", {
108
1179
  ...props,
109
1180
  ref,
110
1181
  className: cn(chatAvatarClass, className)
111
1182
  }));
112
1183
  ChatAvatar.displayName = "Chat.Avatar";
113
- var ChatHeader = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", {
1184
+ var ChatHeader = forwardRef3(({ className, ...props }, ref) => /* @__PURE__ */ jsx4("div", {
114
1185
  ...props,
115
1186
  ref,
116
1187
  className: cn(chatHeaderClass, className)
117
1188
  }));
118
1189
  ChatHeader.displayName = "Chat.Header";
119
- var ChatBubble = forwardRef(({ color, size = "md", filled, streaming, className, children, ...props }, ref) => /* @__PURE__ */ jsxs("div", {
1190
+ var ChatBubble = forwardRef3(({ color, size = "md", filled, streaming, className, children, ...props }, ref) => /* @__PURE__ */ jsxs4("div", {
120
1191
  ...props,
121
1192
  ref,
122
1193
  className: getChatBubbleClasses({
@@ -128,62 +1199,62 @@ var ChatBubble = forwardRef(({ color, size = "md", filled, streaming, className,
128
1199
  }),
129
1200
  children: [
130
1201
  children,
131
- streaming ? /* @__PURE__ */ jsx("span", {
1202
+ streaming ? /* @__PURE__ */ jsx4("span", {
132
1203
  className: "chat-bubble-content chat-bubble-streaming",
133
1204
  "aria-hidden": "true"
134
1205
  }) : null
135
1206
  ]
136
1207
  }));
137
1208
  ChatBubble.displayName = "Chat.Bubble";
138
- var ChatFooter = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", {
1209
+ var ChatFooter = forwardRef3(({ className, ...props }, ref) => /* @__PURE__ */ jsx4("div", {
139
1210
  ...props,
140
1211
  ref,
141
1212
  className: cn(chatFooterClass, className)
142
1213
  }));
143
1214
  ChatFooter.displayName = "Chat.Footer";
144
- var ChatStatus = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", {
1215
+ var ChatStatus = forwardRef3(({ className, ...props }, ref) => /* @__PURE__ */ jsx4("div", {
145
1216
  ...props,
146
1217
  ref,
147
1218
  className: cn(chatStatusClass, className)
148
1219
  }));
149
1220
  ChatStatus.displayName = "Chat.Status";
150
- var ChatStatusItem = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("span", {
1221
+ var ChatStatusItem = forwardRef3(({ className, ...props }, ref) => /* @__PURE__ */ jsx4("span", {
151
1222
  ...props,
152
1223
  ref,
153
1224
  className: cn(chatStatusItemClass, className)
154
1225
  }));
155
1226
  ChatStatusItem.displayName = "Chat.StatusItem";
156
- var ChatStatusValue = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("span", {
1227
+ var ChatStatusValue = forwardRef3(({ className, ...props }, ref) => /* @__PURE__ */ jsx4("span", {
157
1228
  ...props,
158
1229
  ref,
159
1230
  className: cn(chatStatusValueClass, className)
160
1231
  }));
161
1232
  ChatStatusValue.displayName = "Chat.StatusValue";
162
- var ChatActions = forwardRef(({ hover, className, ...props }, ref) => /* @__PURE__ */ jsx("div", {
1233
+ var ChatActions = forwardRef3(({ hover, className, ...props }, ref) => /* @__PURE__ */ jsx4("div", {
163
1234
  ...props,
164
1235
  ref,
165
1236
  className: cn(chatActionsClass, hover && chatActionsHoverClass, className)
166
1237
  }));
167
1238
  ChatActions.displayName = "Chat.Actions";
168
- var ChatScroll = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", {
1239
+ var ChatScroll = forwardRef3(({ className, ...props }, ref) => /* @__PURE__ */ jsx4("div", {
169
1240
  ...props,
170
1241
  ref,
171
1242
  className: cn(chatScrollClass, className)
172
1243
  }));
173
1244
  ChatScroll.displayName = "Chat.Scroll";
174
- var ChatScrollBody = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", {
1245
+ var ChatScrollBody = forwardRef3(({ className, ...props }, ref) => /* @__PURE__ */ jsx4("div", {
175
1246
  ...props,
176
1247
  ref,
177
1248
  className: cn(chatScrollBodyClass, className)
178
1249
  }));
179
1250
  ChatScrollBody.displayName = "Chat.ScrollBody";
180
- var ChatScrollTrack = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", {
1251
+ var ChatScrollTrack = forwardRef3(({ className, ...props }, ref) => /* @__PURE__ */ jsx4("div", {
181
1252
  ...props,
182
1253
  ref,
183
1254
  className: cn(chatScrollTrackClass, className)
184
1255
  }));
185
1256
  ChatScrollTrack.displayName = "Chat.ScrollTrack";
186
- var ChatScrollIndicator = forwardRef(({ targetId, timeline, type = "button", className, onClick, ...props }, ref) => /* @__PURE__ */ jsx("button", {
1257
+ var ChatScrollIndicator = forwardRef3(({ targetId, timeline, type = "button", className, onClick, ...props }, ref) => /* @__PURE__ */ jsx4("button", {
187
1258
  ...props,
188
1259
  ref,
189
1260
  type,
@@ -205,59 +1276,59 @@ var ChatScrollIndicator = forwardRef(({ targetId, timeline, type = "button", cla
205
1276
  }
206
1277
  }));
207
1278
  ChatScrollIndicator.displayName = "Chat.ScrollIndicator";
208
- var ChatReasoning = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("details", {
1279
+ var ChatReasoning = forwardRef3(({ className, ...props }, ref) => /* @__PURE__ */ jsx4("details", {
209
1280
  ...props,
210
1281
  ref,
211
1282
  className: cn(chatReasoningClass, className)
212
1283
  }));
213
1284
  ChatReasoning.displayName = "Chat.Reasoning";
214
- var ChatTool = forwardRef(({ status, className, ...props }, ref) => /* @__PURE__ */ jsx("details", {
1285
+ var ChatTool = forwardRef3(({ status, className, ...props }, ref) => /* @__PURE__ */ jsx4("details", {
215
1286
  ...props,
216
1287
  ref,
217
1288
  className: getChatToolClasses({ status, className })
218
1289
  }));
219
1290
  ChatTool.displayName = "Chat.Tool";
220
- var ChatToolHeader = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("summary", {
1291
+ var ChatToolHeader = forwardRef3(({ className, ...props }, ref) => /* @__PURE__ */ jsx4("summary", {
221
1292
  ...props,
222
1293
  ref,
223
1294
  className: cn(chatToolHeaderClass, className)
224
1295
  }));
225
1296
  ChatToolHeader.displayName = "Chat.ToolHeader";
226
- var ChatToolStatus = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("span", {
1297
+ var ChatToolStatus = forwardRef3(({ className, ...props }, ref) => /* @__PURE__ */ jsx4("span", {
227
1298
  ...props,
228
1299
  ref,
229
1300
  className: cn(chatToolStatusClass, className)
230
1301
  }));
231
1302
  ChatToolStatus.displayName = "Chat.ToolStatus";
232
- var ChatToolCall = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", {
1303
+ var ChatToolCall = forwardRef3(({ className, ...props }, ref) => /* @__PURE__ */ jsx4("div", {
233
1304
  ...props,
234
1305
  ref,
235
1306
  className: cn(chatToolCallClass, className)
236
1307
  }));
237
1308
  ChatToolCall.displayName = "Chat.ToolCall";
238
- var ChatToolResult = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", {
1309
+ var ChatToolResult = forwardRef3(({ className, ...props }, ref) => /* @__PURE__ */ jsx4("div", {
239
1310
  ...props,
240
1311
  ref,
241
1312
  className: cn(chatToolResultClass, className)
242
1313
  }));
243
1314
  ChatToolResult.displayName = "Chat.ToolResult";
244
- var ChatTyping = forwardRef(({
1315
+ var ChatTyping = forwardRef3(({
245
1316
  className,
246
1317
  role = "status",
247
1318
  "aria-label": ariaLabel = "Assistant is typing",
248
1319
  ...props
249
- }, ref) => /* @__PURE__ */ jsx("span", {
1320
+ }, ref) => /* @__PURE__ */ jsx4("span", {
250
1321
  ...props,
251
1322
  ref,
252
1323
  role,
253
1324
  "aria-label": ariaLabel,
254
1325
  className: cn(chatTypingClass, className),
255
- children: /* @__PURE__ */ jsx("span", {
1326
+ children: /* @__PURE__ */ jsx4("span", {
256
1327
  "aria-hidden": "true"
257
1328
  })
258
1329
  }));
259
1330
  ChatTyping.displayName = "Chat.Typing";
260
- var ChatRoot = forwardRef(({ placement = "start", timeline, className, ...props }, ref) => /* @__PURE__ */ jsx("div", {
1331
+ var ChatRoot = forwardRef3(({ placement = "start", timeline, className, ...props }, ref) => /* @__PURE__ */ jsx4("div", {
261
1332
  ...props,
262
1333
  ref,
263
1334
  "data-chat-tl": timeline ?? props["data-chat-tl"],
@@ -265,6 +1336,7 @@ var ChatRoot = forwardRef(({ placement = "start", timeline, className, ...props
265
1336
  }));
266
1337
  ChatRoot.displayName = "Chat";
267
1338
  var Chat = Object.assign(ChatRoot, {
1339
+ Composer: ChatComposer,
268
1340
  Avatar: ChatAvatar,
269
1341
  Header: ChatHeader,
270
1342
  Bubble: ChatBubble,
@@ -290,6 +1362,7 @@ export {
290
1362
  ChatActions,
291
1363
  ChatAvatar,
292
1364
  ChatBubble,
1365
+ ChatComposer,
293
1366
  ChatFooter,
294
1367
  ChatHeader,
295
1368
  ChatReasoning,