@refraction-ui/react 0.23.2 → 0.24.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.
@@ -639,4 +639,33 @@ interface EmojiTriggerOptions {
639
639
  */
640
640
  declare function createEmojiTrigger(options?: EmojiTriggerOptions): ComposerTrigger;
641
641
 
642
- export { type ActiveTriggerState, type AppliedEdit, type ApplyValueEditArgs, CIRCUIT_BREAKER_THRESHOLD, CLOSED_SUGGESTION_STATE, COUNTER_VISIBLE_FRACTION, type CanSendArgs, type CommitTokenArgs, type CommitTokenResult, type ComposerAPI, type ComposerAttachment, type ComposerAttachmentDraft, type ComposerAttachmentKind, type ComposerAttachmentStatus, type ComposerCandidate, type ComposerConfig, type ComposerDraft, type ComposerDraftStore, type ComposerEvent, type ComposerMode, type ComposerOutput, type ComposerSelection, type ComposerState, type ComposerSubmission, type ComposerToken, type ComposerTrigger, type ComposerTriggerResolver, type ComposerTriggerScope, type ComposerValidationResult, type ComposerValidator, type CounterState, DEFAULT_DRAFT_DEBOUNCE_MS, DEFAULT_MAX_LINES, DEFAULT_MAX_QUERY_LENGTH, DEFAULT_MAX_VISIBLE_RESULTS, DEFAULT_MIN_LINES, DEFAULT_TYPING_SIGNAL_INTERVAL_MS, DISMISS_GRACE_MS, type DetectTriggerArgs, type DismissedOccurrence, type EmojiTriggerOptions, type EnterResult, LOADING_DELAY_MS, type PlacedToken, type ResolvedToken, type ResolvedTrigger, SCAN_UNITS_PER_GRAPHEME, type SuggestionController, type SuggestionState, type TextEdit, type TokenRange, type TriggerMatch, type TypedEmojiHit, applyValueEdit, canSend, clampGraphemes, commitTokenAt, composerAccessoryPanelClass, composerAccessoryToggleVariants, composerAttachmentChipVariants, composerCounterVariants, composerFieldClass, composerMenuClass, composerMenuItemVariants, composerPrimaryActionVariants, composerSurfaceVariants, composerTokenPillClass, composerTrayClass, computeCounter, createComposer, createEmojiTrigger, createSuggestionController, detectActiveTrigger, detectTypedEmoji, diffEdit, expandRangeOverTokens, graphemeLength, isGraphemeBoundary, payloadFor, resolveTriggerConfig, scanWindowFor, serializeTokens, shouldSubmitOnEnter, snapSelectionToTokens, toMessageAttachment };
642
+ /** Clipboard payload rules shared by browser adapters; no clipboard access. */
643
+ declare const DEFAULT_COMPOSER_IMAGE_MAX_BYTES: number;
644
+ interface ComposerClipboardFile {
645
+ readonly name: string;
646
+ readonly type: string;
647
+ readonly size: number;
648
+ }
649
+ declare class ComposerImagePasteError extends Error {
650
+ readonly reason: 'empty' | 'max-size';
651
+ readonly fileName: string;
652
+ constructor(reason: 'empty' | 'max-size', fileName: string);
653
+ }
654
+ /** PNG/JPEG only. Reject the entire image batch before handing anything to a host. */
655
+ declare function composerClipboardImages<T extends ComposerClipboardFile>(files: Iterable<T>, maxBytes?: number): T[];
656
+
657
+ /** Structural field seam: the headless package never reads browser globals. */
658
+ interface ComposerPasteField {
659
+ value: string;
660
+ selectionStart: number;
661
+ selectionEnd: number;
662
+ setSelectionRange(start: number, end: number): void;
663
+ }
664
+ /**
665
+ * Apply the core's token/grapheme rules, then mirror its edit as a native undo
666
+ * transaction when the adapter supplies one. The adapter must defer rendering
667
+ * the field until this call returns. Empty image-only pastes preserve selection.
668
+ */
669
+ declare function pasteComposerField(api: ComposerAPI, field: ComposerPasteField, text: string, insertText?: (text: string) => boolean): void;
670
+
671
+ export { type ActiveTriggerState, type AppliedEdit, type ApplyValueEditArgs, CIRCUIT_BREAKER_THRESHOLD, CLOSED_SUGGESTION_STATE, COUNTER_VISIBLE_FRACTION, type CanSendArgs, type CommitTokenArgs, type CommitTokenResult, type ComposerAPI, type ComposerAttachment, type ComposerAttachmentDraft, type ComposerAttachmentKind, type ComposerAttachmentStatus, type ComposerCandidate, type ComposerClipboardFile, type ComposerConfig, type ComposerDraft, type ComposerDraftStore, type ComposerEvent, ComposerImagePasteError, type ComposerMode, type ComposerOutput, type ComposerPasteField, type ComposerSelection, type ComposerState, type ComposerSubmission, type ComposerToken, type ComposerTrigger, type ComposerTriggerResolver, type ComposerTriggerScope, type ComposerValidationResult, type ComposerValidator, type CounterState, DEFAULT_COMPOSER_IMAGE_MAX_BYTES, DEFAULT_DRAFT_DEBOUNCE_MS, DEFAULT_MAX_LINES, DEFAULT_MAX_QUERY_LENGTH, DEFAULT_MAX_VISIBLE_RESULTS, DEFAULT_MIN_LINES, DEFAULT_TYPING_SIGNAL_INTERVAL_MS, DISMISS_GRACE_MS, type DetectTriggerArgs, type DismissedOccurrence, type EmojiTriggerOptions, type EnterResult, LOADING_DELAY_MS, type PlacedToken, type ResolvedToken, type ResolvedTrigger, SCAN_UNITS_PER_GRAPHEME, type SuggestionController, type SuggestionState, type TextEdit, type TokenRange, type TriggerMatch, type TypedEmojiHit, applyValueEdit, canSend, clampGraphemes, commitTokenAt, composerAccessoryPanelClass, composerAccessoryToggleVariants, composerAttachmentChipVariants, composerClipboardImages, composerCounterVariants, composerFieldClass, composerMenuClass, composerMenuItemVariants, composerPrimaryActionVariants, composerSurfaceVariants, composerTokenPillClass, composerTrayClass, computeCounter, createComposer, createEmojiTrigger, createSuggestionController, detectActiveTrigger, detectTypedEmoji, diffEdit, expandRangeOverTokens, graphemeLength, isGraphemeBoundary, pasteComposerField, payloadFor, resolveTriggerConfig, scanWindowFor, serializeTokens, shouldSubmitOnEnter, snapSelectionToTokens, toMessageAttachment };
@@ -21,8 +21,8 @@ declare function createPageHeader(props?: PageHeaderProps): PageHeaderAPI;
21
21
  * on narrow screens. Carries no outer margin — the page layout spaces it.
22
22
  */
23
23
  declare const pageHeaderClass = "flex flex-wrap items-start justify-between gap-4";
24
- /** Text cluster (kicker, title, description). */
25
- declare const pageHeaderTextClass = "flex min-w-0 flex-1 flex-col gap-1";
24
+ /** Give text a preferred width so actions wrap before squeezing the heading. */
25
+ declare const pageHeaderTextClass = "flex min-w-0 basis-64 grow flex-col gap-1";
26
26
  /** Small uppercase eyebrow above the title. */
27
27
  declare const pageHeaderKickerClass = "break-words text-[11px] uppercase tracking-wider text-muted-foreground";
28
28
  /** Title — app-sized, not a marketing display size. */
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
- import { ComposerTrigger, ComposerCandidate, ComposerAttachmentDraft, ComposerDraftStore, ComposerValidator, ComposerSubmission, ComposerAttachment, ComposerEvent, ComposerAPI, ComposerState, ComposerConfig } from '../composer/index.js';
3
- export { ComposerAPI, ComposerAttachment, ComposerAttachmentDraft, ComposerAttachmentKind, ComposerAttachmentStatus, ComposerCandidate, ComposerConfig, ComposerDraft, ComposerDraftStore, ComposerEvent, ComposerMode, ComposerOutput, ComposerSelection, ComposerState, ComposerSubmission, ComposerToken, ComposerTrigger, ComposerTriggerResolver, ComposerTriggerScope, ComposerValidationResult, ComposerValidator, CounterState, EmojiTriggerOptions, EnterResult, PlacedToken, ResolvedToken, SuggestionState, composerAccessoryPanelClass, composerAccessoryToggleVariants, composerAttachmentChipVariants, composerCounterVariants, composerFieldClass, composerMenuClass, composerMenuItemVariants, composerPrimaryActionVariants, composerSurfaceVariants, composerTokenPillClass, composerTrayClass, createComposer, createEmojiTrigger, toMessageAttachment } from '../composer/index.js';
2
+ import { ComposerConfig, ComposerTrigger, ComposerCandidate, ComposerAttachmentDraft, ComposerDraftStore, ComposerValidator, ComposerSubmission, ComposerAttachment, ComposerEvent, ComposerAPI, ComposerState } from '../composer/index.js';
3
+ export { ComposerAPI, ComposerAttachment, ComposerAttachmentDraft, ComposerAttachmentKind, ComposerAttachmentStatus, ComposerCandidate, ComposerConfig, ComposerDraft, ComposerDraftStore, ComposerEvent, ComposerImagePasteError, ComposerMode, ComposerOutput, ComposerSelection, ComposerState, ComposerSubmission, ComposerToken, ComposerTrigger, ComposerTriggerResolver, ComposerTriggerScope, ComposerValidationResult, ComposerValidator, CounterState, DEFAULT_COMPOSER_IMAGE_MAX_BYTES, EmojiTriggerOptions, EnterResult, PlacedToken, ResolvedToken, SuggestionState, composerAccessoryPanelClass, composerAccessoryToggleVariants, composerAttachmentChipVariants, composerClipboardImages, composerCounterVariants, composerFieldClass, composerMenuClass, composerMenuItemVariants, composerPrimaryActionVariants, composerSurfaceVariants, composerTokenPillClass, composerTrayClass, createComposer, createEmojiTrigger, toMessageAttachment } from '../composer/index.js';
4
4
 
5
5
  /** All user-facing text; every entry is overridable via the `strings` prop. */
6
6
  interface RefractionComposerStrings {
@@ -17,6 +17,7 @@ interface RefractionComposerStrings {
17
17
  noMatchesLabel: (query: string) => string;
18
18
  loadingLabel: string;
19
19
  retryLabel: string;
20
+ pasteFailedNotice: string;
20
21
  trimmedNotice: string;
21
22
  counterLabel: (remaining: number) => string;
22
23
  editingLabel: string;
@@ -52,6 +53,8 @@ interface RefractionComposerProps extends Omit<React.HTMLAttributes<HTMLDivEleme
52
53
  maxLines?: number;
53
54
  /** Grapheme-cluster budget (not UTF-16 units). Fixed at mount. */
54
55
  maxLength?: number;
56
+ maxAttachmentSizeBytes?: number;
57
+ acceptAttachment?: ComposerConfig['acceptAttachment'];
55
58
  maxAttachments?: number;
56
59
  disabled?: boolean;
57
60
  readOnly?: boolean;
@@ -109,7 +112,12 @@ interface RefractionComposerProps extends Omit<React.HTMLAttributes<HTMLDivEleme
109
112
  onEditCancel?: () => void;
110
113
  /** Throttled typing signal (core-owned leading-edge throttle). */
111
114
  onTypingActivity?: () => void;
112
- onAttachmentAdd?: (attachment: ComposerAttachment) => void;
115
+ /** Original File is retained for the host's uploader (picker/drop/paste). */
116
+ onAttachmentAdd?: (attachment: ComposerAttachment, file: File) => void;
117
+ /** Opt-in PNG/JPEG clipboard handoff. The host stages/uploads; never sends. */
118
+ onImagesPasted?: (images: File[]) => void;
119
+ /** Clipboard read/size errors; the draft and image batch stay unchanged. */
120
+ onPasteError?: (error: unknown) => void;
113
121
  onAttachmentRejected?: (event: Extract<ComposerEvent, {
114
122
  type: 'attachment-rejected';
115
123
  }>) => void;
@@ -117,9 +117,23 @@ var Avatar = React.forwardRef(
117
117
  var AvatarImage = React.forwardRef(
118
118
  function AvatarImage2({ className, src, alt = "", onLoad, onError, ...props }, ref) {
119
119
  const { imageError, setImageLoaded, setImageError } = React.useContext(AvatarContext);
120
+ const imageRef = React.useRef(null);
121
+ const mergedRef = React.useCallback((node) => {
122
+ imageRef.current = node;
123
+ if (typeof ref === "function") {
124
+ const cleanup = ref(node);
125
+ if (typeof cleanup === "function") {
126
+ return () => {
127
+ imageRef.current = null;
128
+ cleanup();
129
+ };
130
+ }
131
+ } else if (ref) ref.current = node;
132
+ }, [ref]);
120
133
  React.useEffect(() => {
121
- setImageLoaded(false);
122
- setImageError(false);
134
+ const image = imageRef.current;
135
+ setImageLoaded(!!image?.complete && image.naturalWidth > 0);
136
+ setImageError(!!image?.complete && image.naturalWidth === 0);
123
137
  }, [src, setImageLoaded, setImageError]);
124
138
  const handleLoad = (e) => {
125
139
  setImageLoaded(true);
@@ -133,7 +147,7 @@ var AvatarImage = React.forwardRef(
133
147
  return /* @__PURE__ */ jsx(
134
148
  "img",
135
149
  {
136
- ref,
150
+ ref: mergedRef,
137
151
  className: cn(avatarImageVariants(), className),
138
152
  src,
139
153
  alt,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../avatar/src/avatar.ts", "../../avatar/src/avatar.styles.ts", "../../react-avatar/src/avatar.tsx"],
4
- "sourcesContent": ["import type { AccessibilityProps } from '@refraction-ui/shared'\n\nexport type AvatarSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'\n\n/** Outline of the avatar. */\nexport type AvatarShape = 'circle' | 'square'\n\nexport type AvatarLoadingStatus = 'idle' | 'loading' | 'loaded' | 'error'\n\nexport interface AvatarProps {\n src?: string\n alt?: string\n fallback?: string\n size?: AvatarSize\n}\n\nexport interface AvatarAPI {\n /** ARIA attributes to spread on the root element */\n ariaProps: Partial<AccessibilityProps>\n /** Data attributes for CSS styling hooks */\n dataAttributes: Record<string, string>\n /** Image ARIA props */\n imageProps: {\n alt: string\n role: string\n }\n /** Fallback text (initials) */\n fallbackText: string\n /** Current state */\n state: {\n size: AvatarSize\n hasSrc: boolean\n }\n}\n\n/**\n * Extract initials from a name or fallback string.\n * Takes first letter of first two words, uppercased.\n */\nexport function getInitials(name: string): string {\n const parts = name.trim().split(/\\s+/)\n if (parts.length === 0 || parts[0] === '') return ''\n if (parts.length === 1) return parts[0].charAt(0).toUpperCase()\n return (parts[0].charAt(0) + parts[parts.length - 1].charAt(0)).toUpperCase()\n}\n\nexport function createAvatar(props: AvatarProps = {}): AvatarAPI {\n const { src, alt = '', fallback = '', size = 'md' } = props\n\n // role=\"img\" makes its children presentational, so it is only applied when\n // the avatar itself has a name; otherwise the inner <img alt> / fallback\n // text stays exposed.\n const ariaProps: Partial<AccessibilityProps> = {}\n if (alt) {\n ariaProps.role = 'img'\n ariaProps['aria-label'] = alt\n }\n\n const fallbackText = fallback ? getInitials(fallback) : (alt ? getInitials(alt) : '')\n\n const dataAttributes: Record<string, string> = {\n 'data-slot': 'avatar',\n }\n\n return {\n ariaProps,\n dataAttributes,\n imageProps: {\n alt,\n role: 'img',\n },\n fallbackText,\n state: {\n size,\n hasSrc: !!src,\n },\n }\n}\n", "import { cva } from '@refraction-ui/shared'\nimport type { TokenContract } from '@refraction-ui/shared'\n\nexport const avatarTokens: TokenContract = {\n name: 'avatar',\n tokens: {\n bg: { variable: '--rfr-avatar-bg', fallback: 'hsl(var(--muted))' },\n fg: { variable: '--rfr-avatar-fg', fallback: 'hsl(var(--muted-foreground))' },\n border: { variable: '--rfr-avatar-border', fallback: 'hsl(var(--border))' },\n },\n}\n\nexport const avatarVariants = cva({\n base: 'relative flex shrink-0 overflow-hidden',\n variants: {\n shape: {\n circle: 'rounded-full',\n square: 'rounded-md',\n },\n size: {\n xs: 'h-6 w-6',\n sm: 'h-8 w-8',\n md: 'h-10 w-10',\n lg: 'h-12 w-12',\n xl: 'h-16 w-16',\n },\n },\n defaultVariants: {\n shape: 'circle',\n size: 'md',\n },\n})\n\nexport const avatarImageVariants = cva({\n base: 'aspect-square h-full w-full object-cover',\n})\n\nexport const avatarFallbackVariants = cva({\n base: 'flex h-full w-full items-center justify-center rounded-[inherit] bg-muted text-muted-foreground',\n variants: {\n size: {\n xs: 'text-xs',\n sm: 'text-xs',\n md: 'text-sm',\n lg: 'text-base',\n xl: 'text-lg',\n },\n },\n defaultVariants: {\n size: 'md',\n },\n})\n", "import * as React from 'react'\nimport {\n createAvatar,\n avatarVariants,\n avatarImageVariants,\n avatarFallbackVariants,\n type AvatarSize,\n type AvatarShape,\n} from '@refraction-ui/avatar'\nimport { cn } from '@refraction-ui/shared'\n\n/* ─── Context ──────────────────────────────────────────────────── */\ninterface AvatarContextValue {\n size: AvatarSize\n imageLoaded: boolean\n imageError: boolean\n setImageLoaded: (loaded: boolean) => void\n setImageError: (error: boolean) => void\n}\n\nconst AvatarContext = React.createContext<AvatarContextValue>({\n size: 'md',\n imageLoaded: false,\n imageError: false,\n setImageLoaded: () => {},\n setImageError: () => {},\n})\n\n/* ─── Avatar (root) ────────────────────────────────────────────── */\nexport interface AvatarProps extends React.HTMLAttributes<HTMLSpanElement> {\n size?: AvatarSize\n /** `circle` (default) or `square` (rounded corners) — e.g. for teams and organisations. */\n shape?: AvatarShape\n}\n\n/**\n * Avatar -- circular image with fallback support.\n * Compound component: use Avatar > AvatarImage + AvatarFallback.\n */\nexport const Avatar = React.forwardRef<HTMLSpanElement, AvatarProps>(\n function Avatar({ size = 'md', shape = 'circle', className, children, ...props }, ref) {\n const [imageLoaded, setImageLoaded] = React.useState(false)\n const [imageError, setImageError] = React.useState(false)\n\n const api = createAvatar({ size, alt: props['aria-label'] })\n\n return (\n <AvatarContext.Provider value={{ size, imageLoaded, imageError, setImageLoaded, setImageError }}>\n <span\n ref={ref}\n className={cn(avatarVariants({ size, shape }), className)}\n {...api.ariaProps}\n {...api.dataAttributes}\n {...props}\n >\n {children}\n </span>\n </AvatarContext.Provider>\n )\n },\n)\n\n/* ─── AvatarImage ──────────────────────────────────────────────── */\nexport interface AvatarImageProps extends React.ImgHTMLAttributes<HTMLImageElement> {}\n\nexport const AvatarImage = React.forwardRef<HTMLImageElement, AvatarImageProps>(\n function AvatarImage({ className, src, alt = '', onLoad, onError, ...props }, ref) {\n const { imageError, setImageLoaded, setImageError } = React.useContext(AvatarContext)\n\n React.useEffect(() => {\n setImageLoaded(false)\n setImageError(false)\n }, [src, setImageLoaded, setImageError])\n\n const handleLoad = (e: React.SyntheticEvent<HTMLImageElement>) => {\n setImageLoaded(true)\n onLoad?.(e)\n }\n\n const handleError = (e: React.SyntheticEvent<HTMLImageElement>) => {\n setImageError(true)\n onError?.(e)\n }\n\n if (imageError) return null\n\n return (\n <img\n ref={ref}\n className={cn(avatarImageVariants(), className)}\n src={src}\n alt={alt}\n onLoad={handleLoad}\n onError={handleError}\n {...props}\n />\n )\n },\n)\n\n/* ─── AvatarFallback ───────────────────────────────────────────── */\nexport interface AvatarFallbackProps extends React.HTMLAttributes<HTMLSpanElement> {}\n\nexport const AvatarFallback = React.forwardRef<HTMLSpanElement, AvatarFallbackProps>(\n function AvatarFallback({ className, children, ...props }, ref) {\n const { size, imageLoaded, imageError } = React.useContext(AvatarContext)\n\n if (imageLoaded && !imageError) return null\n\n return (\n <span\n ref={ref}\n className={cn(avatarFallbackVariants({ size }), className)}\n {...props}\n >\n {children}\n </span>\n )\n },\n)\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { AccessibilityProps } from '@refraction-ui/shared'\n\nexport type AvatarSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'\n\n/** Outline of the avatar. */\nexport type AvatarShape = 'circle' | 'square'\n\nexport type AvatarLoadingStatus = 'idle' | 'loading' | 'loaded' | 'error'\n\nexport interface AvatarProps {\n src?: string\n alt?: string\n fallback?: string\n size?: AvatarSize\n}\n\nexport interface AvatarAPI {\n /** ARIA attributes to spread on the root element */\n ariaProps: Partial<AccessibilityProps>\n /** Data attributes for CSS styling hooks */\n dataAttributes: Record<string, string>\n /** Image ARIA props */\n imageProps: {\n alt: string\n role: string\n }\n /** Fallback text (initials) */\n fallbackText: string\n /** Current state */\n state: {\n size: AvatarSize\n hasSrc: boolean\n }\n}\n\n/**\n * Extract initials from a name or fallback string.\n * Takes first letter of first two words, uppercased.\n */\nexport function getInitials(name: string): string {\n const parts = name.trim().split(/\\s+/)\n if (parts.length === 0 || parts[0] === '') return ''\n if (parts.length === 1) return parts[0].charAt(0).toUpperCase()\n return (parts[0].charAt(0) + parts[parts.length - 1].charAt(0)).toUpperCase()\n}\n\nexport function createAvatar(props: AvatarProps = {}): AvatarAPI {\n const { src, alt = '', fallback = '', size = 'md' } = props\n\n // role=\"img\" makes its children presentational, so it is only applied when\n // the avatar itself has a name; otherwise the inner <img alt> / fallback\n // text stays exposed.\n const ariaProps: Partial<AccessibilityProps> = {}\n if (alt) {\n ariaProps.role = 'img'\n ariaProps['aria-label'] = alt\n }\n\n const fallbackText = fallback ? getInitials(fallback) : (alt ? getInitials(alt) : '')\n\n const dataAttributes: Record<string, string> = {\n 'data-slot': 'avatar',\n }\n\n return {\n ariaProps,\n dataAttributes,\n imageProps: {\n alt,\n role: 'img',\n },\n fallbackText,\n state: {\n size,\n hasSrc: !!src,\n },\n }\n}\n", "import { cva } from '@refraction-ui/shared'\nimport type { TokenContract } from '@refraction-ui/shared'\n\nexport const avatarTokens: TokenContract = {\n name: 'avatar',\n tokens: {\n bg: { variable: '--rfr-avatar-bg', fallback: 'hsl(var(--muted))' },\n fg: { variable: '--rfr-avatar-fg', fallback: 'hsl(var(--muted-foreground))' },\n border: { variable: '--rfr-avatar-border', fallback: 'hsl(var(--border))' },\n },\n}\n\nexport const avatarVariants = cva({\n base: 'relative flex shrink-0 overflow-hidden',\n variants: {\n shape: {\n circle: 'rounded-full',\n square: 'rounded-md',\n },\n size: {\n xs: 'h-6 w-6',\n sm: 'h-8 w-8',\n md: 'h-10 w-10',\n lg: 'h-12 w-12',\n xl: 'h-16 w-16',\n },\n },\n defaultVariants: {\n shape: 'circle',\n size: 'md',\n },\n})\n\nexport const avatarImageVariants = cva({\n base: 'aspect-square h-full w-full object-cover',\n})\n\nexport const avatarFallbackVariants = cva({\n base: 'flex h-full w-full items-center justify-center rounded-[inherit] bg-muted text-muted-foreground',\n variants: {\n size: {\n xs: 'text-xs',\n sm: 'text-xs',\n md: 'text-sm',\n lg: 'text-base',\n xl: 'text-lg',\n },\n },\n defaultVariants: {\n size: 'md',\n },\n})\n", "import * as React from 'react'\nimport {\n createAvatar,\n avatarVariants,\n avatarImageVariants,\n avatarFallbackVariants,\n type AvatarSize,\n type AvatarShape,\n} from '@refraction-ui/avatar'\nimport { cn } from '@refraction-ui/shared'\n\n/* ─── Context ──────────────────────────────────────────────────── */\ninterface AvatarContextValue {\n size: AvatarSize\n imageLoaded: boolean\n imageError: boolean\n setImageLoaded: (loaded: boolean) => void\n setImageError: (error: boolean) => void\n}\n\nconst AvatarContext = React.createContext<AvatarContextValue>({\n size: 'md',\n imageLoaded: false,\n imageError: false,\n setImageLoaded: () => {},\n setImageError: () => {},\n})\n\n/* ─── Avatar (root) ────────────────────────────────────────────── */\nexport interface AvatarProps extends React.HTMLAttributes<HTMLSpanElement> {\n size?: AvatarSize\n /** `circle` (default) or `square` (rounded corners) — e.g. for teams and organisations. */\n shape?: AvatarShape\n}\n\n/**\n * Avatar -- circular image with fallback support.\n * Compound component: use Avatar > AvatarImage + AvatarFallback.\n */\nexport const Avatar = React.forwardRef<HTMLSpanElement, AvatarProps>(\n function Avatar({ size = 'md', shape = 'circle', className, children, ...props }, ref) {\n const [imageLoaded, setImageLoaded] = React.useState(false)\n const [imageError, setImageError] = React.useState(false)\n\n const api = createAvatar({ size, alt: props['aria-label'] })\n\n return (\n <AvatarContext.Provider value={{ size, imageLoaded, imageError, setImageLoaded, setImageError }}>\n <span\n ref={ref}\n className={cn(avatarVariants({ size, shape }), className)}\n {...api.ariaProps}\n {...api.dataAttributes}\n {...props}\n >\n {children}\n </span>\n </AvatarContext.Provider>\n )\n },\n)\n\n/* ─── AvatarImage ──────────────────────────────────────────────── */\nexport interface AvatarImageProps extends React.ImgHTMLAttributes<HTMLImageElement> {}\n\nexport const AvatarImage = React.forwardRef<HTMLImageElement, AvatarImageProps>(\n function AvatarImage({ className, src, alt = '', onLoad, onError, ...props }, ref) {\n const { imageError, setImageLoaded, setImageError } = React.useContext(AvatarContext)\n\n const imageRef = React.useRef<HTMLImageElement | null>(null)\n const mergedRef = React.useCallback((node: HTMLImageElement | null) => {\n imageRef.current = node\n if (typeof ref === 'function') {\n const cleanup: unknown = ref(node)\n if (typeof cleanup === 'function') {\n return () => {\n imageRef.current = null\n cleanup()\n }\n }\n } else if (ref) ref.current = node\n }, [ref])\n\n React.useEffect(() => {\n // SSR images may finish before hydration installs event handlers. Read\n // the native result instead of erasing a cached success or failure.\n const image = imageRef.current\n setImageLoaded(!!image?.complete && image.naturalWidth > 0)\n setImageError(!!image?.complete && image.naturalWidth === 0)\n }, [src, setImageLoaded, setImageError])\n\n const handleLoad = (e: React.SyntheticEvent<HTMLImageElement>) => {\n setImageLoaded(true)\n onLoad?.(e)\n }\n\n const handleError = (e: React.SyntheticEvent<HTMLImageElement>) => {\n setImageError(true)\n onError?.(e)\n }\n\n if (imageError) return null\n\n return (\n <img\n ref={mergedRef}\n className={cn(avatarImageVariants(), className)}\n src={src}\n alt={alt}\n onLoad={handleLoad}\n onError={handleError}\n {...props}\n />\n )\n },\n)\n\n/* ─── AvatarFallback ───────────────────────────────────────────── */\nexport interface AvatarFallbackProps extends React.HTMLAttributes<HTMLSpanElement> {}\n\nexport const AvatarFallback = React.forwardRef<HTMLSpanElement, AvatarFallbackProps>(\n function AvatarFallback({ className, children, ...props }, ref) {\n const { size, imageLoaded, imageError } = React.useContext(AvatarContext)\n\n if (imageLoaded && !imageError) return null\n\n return (\n <span\n ref={ref}\n className={cn(avatarFallbackVariants({ size }), className)}\n {...props}\n >\n {children}\n </span>\n )\n },\n)\n"],
5
+ "mappings": "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",
6
6
  "names": ["Avatar", "AvatarImage", "AvatarFallback"]
7
7
  }
@@ -1233,6 +1233,54 @@ function createEmojiTrigger(options = {}) {
1233
1233
  }
1234
1234
  };
1235
1235
  }
1236
+ var DEFAULT_COMPOSER_IMAGE_MAX_BYTES = 100 * 1024 * 1024;
1237
+ var ComposerImagePasteError = class extends Error {
1238
+ constructor(reason, fileName) {
1239
+ super(`Cannot paste image ${fileName}: ${reason}`);
1240
+ this.reason = reason;
1241
+ this.fileName = fileName;
1242
+ this.name = "ComposerImagePasteError";
1243
+ }
1244
+ reason;
1245
+ fileName;
1246
+ };
1247
+ function composerClipboardImages(files, maxBytes = DEFAULT_COMPOSER_IMAGE_MAX_BYTES) {
1248
+ const images = Array.from(files).filter((file) => file.type === "image/png" || file.type === "image/jpeg");
1249
+ for (const image of images) {
1250
+ if (image.size === 0) throw new ComposerImagePasteError("empty", image.name);
1251
+ if (image.size > maxBytes) throw new ComposerImagePasteError("max-size", image.name);
1252
+ }
1253
+ return images;
1254
+ }
1255
+ function splitsSurrogate(text, offset) {
1256
+ return /^[\uD800-\uDBFF][\uDC00-\uDFFF]$/.test(text.slice(offset - 1, offset + 1));
1257
+ }
1258
+ function pasteComposerField(api, field, text, insertText) {
1259
+ api.setSelection({ start: field.selectionStart, end: field.selectionEnd });
1260
+ if (text === "") return;
1261
+ const before = field.value;
1262
+ api.pasteText(text);
1263
+ const after = api.getState();
1264
+ if (after.value !== before) {
1265
+ let start = 0;
1266
+ while (start < before.length && start < after.value.length && before[start] === after.value[start]) start++;
1267
+ if (splitsSurrogate(before, start) || splitsSurrogate(after.value, start)) start--;
1268
+ let end = before.length;
1269
+ let nextEnd = after.value.length;
1270
+ while (end > start && nextEnd > start && before[end - 1] === after.value[nextEnd - 1]) {
1271
+ end--;
1272
+ nextEnd--;
1273
+ }
1274
+ if (splitsSurrogate(before, end) || splitsSurrogate(after.value, nextEnd)) {
1275
+ end++;
1276
+ nextEnd++;
1277
+ }
1278
+ field.setSelectionRange(start, end);
1279
+ insertText?.(after.value.slice(start, nextEnd));
1280
+ if (field.value !== after.value) field.value = after.value;
1281
+ }
1282
+ field.setSelectionRange(after.selection.start, after.selection.end);
1283
+ }
1236
1284
 
1237
1285
  // ../react-composer/dist/index.js
1238
1286
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
@@ -1287,6 +1335,7 @@ var DEFAULT_COMPOSER_STRINGS = {
1287
1335
  noMatchesLabel: (query) => `No matches for "${query}"`,
1288
1336
  loadingLabel: "Loading suggestions\u2026",
1289
1337
  retryLabel: "Retry",
1338
+ pasteFailedNotice: "Could not paste images. Try attaching the file instead.",
1290
1339
  trimmedNotice: "Pasted text was trimmed to fit the length limit",
1291
1340
  counterLabel: (remaining) => `${remaining} characters remaining`,
1292
1341
  editingLabel: "Editing message",
@@ -1359,6 +1408,8 @@ var RefractionComposer = React2.forwardRef(
1359
1408
  maxLines = 6,
1360
1409
  maxLength,
1361
1410
  maxAttachments,
1411
+ maxAttachmentSizeBytes,
1412
+ acceptAttachment,
1362
1413
  disabled = false,
1363
1414
  readOnly = false,
1364
1415
  busy = false,
@@ -1387,6 +1438,8 @@ var RefractionComposer = React2.forwardRef(
1387
1438
  onEditCancel,
1388
1439
  onTypingActivity,
1389
1440
  onAttachmentAdd,
1441
+ onImagesPasted,
1442
+ onPasteError,
1390
1443
  onAttachmentRejected,
1391
1444
  onEvent,
1392
1445
  apiRef,
@@ -1403,6 +1456,9 @@ var RefractionComposer = React2.forwardRef(
1403
1456
  const onStopRef = useLatestRef(onStop);
1404
1457
  const onEventRef = useLatestRef(onEvent);
1405
1458
  const onTypingActivityRef = useLatestRef(onTypingActivity);
1459
+ const onImagesPastedRef = useLatestRef(onImagesPasted);
1460
+ const onPasteErrorRef = useLatestRef(onPasteError);
1461
+ const [pasteFailed, setPasteFailed] = React2.useState(false);
1406
1462
  const onAttachmentAddRef = useLatestRef(onAttachmentAdd);
1407
1463
  const onAttachmentRejectedRef = useLatestRef(onAttachmentRejected);
1408
1464
  const onEditLastRequestedRef = useLatestRef(onEditLastRequested);
@@ -1446,6 +1502,8 @@ var RefractionComposer = React2.forwardRef(
1446
1502
  }
1447
1503
  configRef.current.maxLength = maxLength;
1448
1504
  configRef.current.maxAttachments = maxAttachments;
1505
+ configRef.current.maxAttachmentSizeBytes = maxAttachmentSizeBytes;
1506
+ configRef.current.acceptAttachment = acceptAttachment;
1449
1507
  configRef.current.validator = validator;
1450
1508
  configRef.current.replyToMessageId = replyToMessageId;
1451
1509
  configRef.current.draftStore = draftStore;
@@ -1464,6 +1522,7 @@ var RefractionComposer = React2.forwardRef(
1464
1522
  exitTimers.clear();
1465
1523
  };
1466
1524
  }, []);
1525
+ const pastingRef = React2.useRef(false);
1467
1526
  const textareaRef = React2.useRef(null);
1468
1527
  const highlightRef = React2.useRef(null);
1469
1528
  const rootRef = React2.useRef(null);
@@ -1475,7 +1534,8 @@ var RefractionComposer = React2.forwardRef(
1475
1534
  },
1476
1535
  [forwardedRef]
1477
1536
  );
1478
- const idBase = React2.useRef(id ?? generateId("composer")).current;
1537
+ const reactId = React2.useId();
1538
+ const idBase = React2.useRef(id ?? `composer-${reactId}`).current;
1479
1539
  const listId = `${idBase}-listbox`;
1480
1540
  const optionId = (index) => `${idBase}-option-${index}`;
1481
1541
  React2.useEffect(() => {
@@ -1544,10 +1604,12 @@ var RefractionComposer = React2.forwardRef(
1544
1604
  end: el.selectionEnd ?? el.value.length
1545
1605
  });
1546
1606
  const handleChange = (event) => {
1607
+ if (pastingRef.current) return;
1547
1608
  const el = event.currentTarget;
1548
1609
  withValueChange(() => api.setValue(el.value, readSelection(el)));
1549
1610
  };
1550
1611
  const handleSelect = (event) => {
1612
+ if (pastingRef.current) return;
1551
1613
  const el = event.currentTarget;
1552
1614
  const next = readSelection(el);
1553
1615
  const current = api.getState().selection;
@@ -1639,7 +1701,7 @@ var RefractionComposer = React2.forwardRef(
1639
1701
  continue;
1640
1702
  }
1641
1703
  const added = api.getState().attachments.find((a) => a.id === attachmentId);
1642
- if (added) onAttachmentAddRef.current?.(added);
1704
+ if (added) onAttachmentAddRef.current?.(added, file);
1643
1705
  }
1644
1706
  },
1645
1707
  [api, onAttachmentAddRef]
@@ -1672,14 +1734,40 @@ var RefractionComposer = React2.forwardRef(
1672
1734
  [api]
1673
1735
  );
1674
1736
  const handlePaste = (event) => {
1675
- const files = event.clipboardData?.files;
1676
- if (files && files.length > 0) {
1677
- event.preventDefault();
1737
+ event.preventDefault();
1738
+ const current = api.getState();
1739
+ if (current.disabled || current.readOnly || current.isComposing) return;
1740
+ setPasteFailed(false);
1741
+ let files;
1742
+ let images;
1743
+ let text;
1744
+ try {
1745
+ files = Array.from(event.clipboardData?.files ?? []);
1746
+ text = event.clipboardData?.getData("text/plain") ?? "";
1747
+ if (onImagesPastedRef.current) images = composerClipboardImages(files, maxAttachmentSizeBytes);
1748
+ } catch (error) {
1749
+ setPasteFailed(true);
1750
+ onPasteErrorRef.current?.(error);
1751
+ return;
1752
+ }
1753
+ if (images === void 0 && files.length > 0) {
1678
1754
  addFiles(files);
1679
1755
  return;
1680
1756
  }
1681
- event.preventDefault();
1682
- withValueChange(() => api.pasteText(event.clipboardData?.getData("text/plain") ?? ""));
1757
+ pastingRef.current = true;
1758
+ try {
1759
+ withValueChange(() => pasteComposerField(
1760
+ api,
1761
+ event.currentTarget,
1762
+ text,
1763
+ (inserted) => typeof document.execCommand === "function" && document.execCommand("insertText", false, inserted)
1764
+ ));
1765
+ } finally {
1766
+ pastingRef.current = false;
1767
+ }
1768
+ if (images !== void 0) {
1769
+ if (images.length > 0) onImagesPastedRef.current?.(images);
1770
+ }
1683
1771
  };
1684
1772
  const [dragActive, setDragActive] = React2.useState(false);
1685
1773
  const dragHasPayload = (event) => {
@@ -1880,6 +1968,7 @@ var RefractionComposer = React2.forwardRef(
1880
1968
  }
1881
1969
  )
1882
1970
  ] }),
1971
+ pasteFailed && /* @__PURE__ */ jsx("div", { role: "alert", className: "px-3 pt-2 text-xs text-destructive", children: strings.pasteFailedNotice }),
1883
1972
  trimmedNoticeVisible && /* @__PURE__ */ jsx("div", { role: "status", className: "px-3 pt-2 text-xs text-muted-foreground", children: strings.trimmedNotice }),
1884
1973
  state.attachments.length > 0 && /* @__PURE__ */ jsx("div", { className: composerTrayClass, children: state.attachments.map((attachment) => /* @__PURE__ */ jsxs(
1885
1974
  "span",
@@ -2015,11 +2104,14 @@ var RefractionComposer = React2.forwardRef(
2015
2104
  }
2016
2105
  );
2017
2106
  export {
2107
+ ComposerImagePasteError,
2108
+ DEFAULT_COMPOSER_IMAGE_MAX_BYTES,
2018
2109
  DEFAULT_COMPOSER_STRINGS,
2019
2110
  RefractionComposer,
2020
2111
  composerAccessoryPanelClass,
2021
2112
  composerAccessoryToggleVariants,
2022
2113
  composerAttachmentChipVariants,
2114
+ composerClipboardImages,
2023
2115
  composerCounterVariants,
2024
2116
  composerFieldClass,
2025
2117
  composerMenuClass,