@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.
- package/dist/index.cjs +1271 -1300
- package/dist/index.cjs.map +1 -1
- package/dist/internal/composer/index.d.ts +30 -1
- package/dist/internal/page-header/index.d.ts +2 -2
- package/dist/internal/react-composer/index.d.ts +11 -3
- package/dist/react-avatar.js +17 -3
- package/dist/react-avatar.js.map +2 -2
- package/dist/react-composer.js +99 -7
- package/dist/react-composer.js.map +3 -3
- package/dist/react-dialog.js +53 -31
- package/dist/react-dialog.js.map +2 -2
- package/dist/react-dropdown-menu.js +32 -102
- package/dist/react-dropdown-menu.js.map +2 -2
- package/dist/react-page-header.js +1 -1
- package/dist/react-page-header.js.map +1 -1
- package/dist/react-tabs.js +4 -96
- package/dist/react-tabs.js.map +2 -2
- package/package.json +1 -1
|
@@ -639,4 +639,33 @@ interface EmojiTriggerOptions {
|
|
|
639
639
|
*/
|
|
640
640
|
declare function createEmojiTrigger(options?: EmojiTriggerOptions): ComposerTrigger;
|
|
641
641
|
|
|
642
|
-
|
|
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
|
-
/**
|
|
25
|
-
declare const pageHeaderTextClass = "flex min-w-0
|
|
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
|
|
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
|
-
|
|
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;
|
package/dist/react-avatar.js
CHANGED
|
@@ -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
|
-
|
|
122
|
-
|
|
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,
|
package/dist/react-avatar.js.map
CHANGED
|
@@ -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(
|
|
5
|
-
"mappings": ";;;;;;;;;AAuCO,SAAS,YAAY,MAAsB;AAChD,QAAM,QAAQ,KAAK,KAAA,EAAO,MAAM,KAAK;AACrC,MAAI,MAAM,WAAW,KAAK,MAAM,CAAC,MAAM,GAAI,QAAO;AAClD,MAAI,MAAM,WAAW,EAAG,QAAO,MAAM,CAAC,EAAE,OAAO,CAAC,EAAE,YAAA;AAClD,UAAQ,MAAM,CAAC,EAAE,OAAO,CAAC,IAAI,MAAM,MAAM,SAAS,CAAC,EAAE,OAAO,CAAC,GAAG,YAAA;AAClE;AAEO,SAAS,aAAa,QAAqB,CAAA,GAAe;AAC/D,QAAM,EAAE,KAAK,MAAM,IAAI,WAAW,IAAI,OAAO,KAAA,IAAS;AAKtD,QAAM,YAAyC,CAAA;AAC/C,MAAI,KAAK;AACP,cAAU,OAAO;AACjB,cAAU,YAAY,IAAI;EAC5B;AAEA,QAAM,eAAe,WAAW,YAAY,QAAQ,IAAK,MAAM,YAAY,GAAG,IAAI;AAElF,QAAM,iBAAyC;IAC7C,aAAa;EAAA;AAGf,SAAO;IACL;IACA;IACA,YAAY;MACV;MACA,MAAM;IAAA;IAER;IACA,OAAO;MACL;MACA,QAAQ,CAAC,CAAC;IAAA;EACZ;AAEJ;AC1EO,IAAM,eAA8B;EACzC,MAAM;EACN,QAAQ;IACN,IAAI,EAAE,UAAU,mBAAmB,UAAU,oBAAA;IAC7C,IAAI,EAAE,UAAU,mBAAmB,UAAU,+BAAA;IAC7C,QAAQ,EAAE,UAAU,uBAAuB,UAAU,qBAAA;EAAqB;AAE9E;AAEO,IAAM,iBAAiB,IAAI;EAChC,MAAM;EACN,UAAU;IACR,OAAO;MACL,QAAQ;MACR,QAAQ;IAAA;IAEV,MAAM;MACJ,IAAI;MACJ,IAAI;MACJ,IAAI;MACJ,IAAI;MACJ,IAAI;IAAA;EACN;EAEF,iBAAiB;IACf,OAAO;IACP,MAAM;EAAA;AAEV,CAAC;AAEM,IAAM,sBAAsB,IAAI;EACrC,MAAM;AACR,CAAC;AAEM,IAAM,yBAAyB,IAAI;EACxC,MAAM;EACN,UAAU;IACR,MAAM;MACJ,IAAI;MACJ,IAAI;MACJ,IAAI;MACJ,IAAI;MACJ,IAAI;IAAA;EACN;EAEF,iBAAiB;IACf,MAAM;EAAA;AAEV,CAAC;;;;AC/BD,IAAM,gBAAsB,oBAAkC;EAC5D,MAAM;EACN,aAAa;EACb,YAAY;EACZ,gBAAgB,MAAM;EAAC;EACvB,eAAe,MAAM;EAAC;AACxB,CAAC;AAaM,IAAM,SAAe;EAC1B,SAASA,QAAO,EAAE,OAAO,MAAM,QAAQ,UAAU,WAAW,UAAU,GAAG,MAAA,GAAS,KAAK;AACrF,UAAM,CAAC,aAAa,cAAc,IAAU,eAAS,KAAK;AAC1D,UAAM,CAAC,YAAY,aAAa,IAAU,eAAS,KAAK;AAExD,UAAM,MAAM,aAAa,EAAE,MAAM,KAAK,MAAM,YAAY,EAAA,CAAG;AAE3D,WACE,oBAAC,cAAc,UAAd,EAAuB,OAAO,EAAE,MAAM,aAAa,YAAY,gBAAgB,cAAA,GAC9E,UAAA;MAAC;MAAA;QACC;QACA,WAAW,GAAG,eAAe,EAAE,MAAM,MAAA,CAAO,GAAG,SAAS;QACvD,GAAG,IAAI;QACP,GAAG,IAAI;QACP,GAAG;QAEH;MAAA;IAAA,EACH,CACF;EAEJ;AACF;AAKO,IAAM,cAAoB;EAC/B,SAASC,aAAY,EAAE,WAAW,KAAK,MAAM,IAAI,QAAQ,SAAS,GAAG,MAAA,GAAS,KAAK;AACjF,UAAM,EAAE,YAAY,gBAAgB,cAAA,IAAwB,iBAAW,aAAa;
|
|
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
|
}
|
package/dist/react-composer.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
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
|
-
|
|
1682
|
-
|
|
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,
|