@ahrowe/ui 0.49.0 → 0.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +3 -0
- package/dist/cjs-types/common/chatComposer/chatComposer.d.ts +4 -0
- package/dist/cjs-types/common/chatComposer/chatComposer.types.d.ts +40 -0
- package/dist/cjs-types/common/chatComposer/index.d.ts +2 -0
- package/dist/cjs-types/common/chatMessage/chatMessage.d.ts +4 -0
- package/dist/cjs-types/common/chatMessage/chatMessage.types.d.ts +60 -0
- package/dist/cjs-types/common/chatMessage/index.d.ts +2 -0
- package/dist/cjs-types/common/chatMessageList/chatMessageList.d.ts +4 -0
- package/dist/cjs-types/common/chatMessageList/chatMessageList.types.d.ts +27 -0
- package/dist/cjs-types/common/chatMessageList/index.d.ts +2 -0
- package/dist/cjs-types/common/configProvider/configProvider.types.d.ts +6 -0
- package/dist/cjs-types/common/hooks/useAutoGrow.d.ts +12 -0
- package/dist/cjs-types/common/tabHeader/tabHeader.types.d.ts +5 -3
- package/dist/cjs-types/index.d.ts +6 -0
- package/dist/esm/common/chatComposer/chatComposer.mjs +2 -0
- package/dist/esm/common/chatComposer/chatComposer.mjs.map +1 -0
- package/dist/esm/common/chatComposer/chatComposer.module.mjs +2 -0
- package/dist/esm/common/chatComposer/chatComposer.module.mjs.map +1 -0
- package/dist/esm/common/chatMessage/chatMessage.mjs +2 -0
- package/dist/esm/common/chatMessage/chatMessage.mjs.map +1 -0
- package/dist/esm/common/chatMessage/chatMessage.module.mjs +2 -0
- package/dist/esm/common/chatMessage/chatMessage.module.mjs.map +1 -0
- package/dist/esm/common/chatMessage/chatMessage.types.mjs +2 -0
- package/dist/esm/common/chatMessage/chatMessage.types.mjs.map +1 -0
- package/dist/esm/common/chatMessageList/chatMessageList.mjs +2 -0
- package/dist/esm/common/chatMessageList/chatMessageList.mjs.map +1 -0
- package/dist/esm/common/chatMessageList/chatMessageList.module.mjs +2 -0
- package/dist/esm/common/chatMessageList/chatMessageList.module.mjs.map +1 -0
- package/dist/esm/common/dropdown/dropdown.mjs +1 -1
- package/dist/esm/common/hooks/useAutoGrow.mjs +2 -0
- package/dist/esm/common/hooks/useAutoGrow.mjs.map +1 -0
- package/dist/esm/common/infiniteBlock/useInfiniteScroll.mjs +1 -1
- package/dist/esm/common/infiniteBlock/useInfiniteScroll.mjs.map +1 -1
- package/dist/esm/common/input/input.mjs +1 -1
- package/dist/esm/common/input/input.mjs.map +1 -1
- package/dist/esm/common/tabHeader/tabHeader.mjs +1 -1
- package/dist/esm/common/tabHeader/tabHeader.mjs.map +1 -1
- package/dist/esm/index.mjs +1 -1
- package/dist/index.cjs +3 -3
- package/dist/index.cjs.map +1 -1
- package/dist/style.css +1 -1
- package/dist/types/common/chatComposer/chatComposer.d.ts +4 -0
- package/dist/types/common/chatComposer/chatComposer.types.d.ts +40 -0
- package/dist/types/common/chatComposer/index.d.ts +2 -0
- package/dist/types/common/chatMessage/chatMessage.d.ts +4 -0
- package/dist/types/common/chatMessage/chatMessage.types.d.ts +60 -0
- package/dist/types/common/chatMessage/index.d.ts +2 -0
- package/dist/types/common/chatMessageList/chatMessageList.d.ts +4 -0
- package/dist/types/common/chatMessageList/chatMessageList.types.d.ts +27 -0
- package/dist/types/common/chatMessageList/index.d.ts +2 -0
- package/dist/types/common/configProvider/configProvider.types.d.ts +6 -0
- package/dist/types/common/hooks/useAutoGrow.d.ts +12 -0
- package/dist/types/common/tabHeader/tabHeader.types.d.ts +5 -3
- package/dist/types/index.d.ts +6 -0
- package/docs/CLAUDE.md +6 -3
- package/docs/ChatComposer.md +100 -0
- package/docs/ChatMessage.md +88 -0
- package/docs/ChatMessageList.md +113 -0
- package/docs/ConfigProvider.md +1 -0
- package/docs/TabHeader.md +19 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @ahrowe/ui
|
|
2
2
|
|
|
3
|
+
## 0.50.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- d7ce064: Added `ChatComposer`: the message field with its send button, which sends on Enter (a new line on a touch screen), never on the Enter that confirms an IME word, grows to seven lines, moves buttons of your own to a row below once the text wraps, and swaps to a stop button through `onStop` while an AI answer streams. See docs/ChatComposer.md
|
|
8
|
+
- d7ce064: Added `ChatMessageList`: the scrolling conversation, which follows new and streaming messages while the user is at the bottom, holds still while they read further up, offers a button back down, and loads older messages at the top through `onLoadMore` without moving the view, in Safari too. See docs/ChatMessageList.md
|
|
9
|
+
- d7ce064: Added `ChatMessage`: one message of a chat as a bubble, or without one for an AI answer, with author, avatar, time, delivery status (`ChatMessageStatus`) and actions, and `isContinued` for a run from the same author. See docs/ChatMessage.md
|
|
10
|
+
- 9bb5c12: Added `endContent` to `TabHeaderTab`, shown after the labels and still visible when a narrow screen collapses the tab to its icon, with an `endContent` slot. See docs/TabHeader.md
|
|
11
|
+
- 9bb5c12: `TabHeaderTab`'s `title` and `subtitle` now take any `ReactNode`, not only a string
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- d7ce064: Fixed `Textarea` with `autoResize` keeping its old height when a change of width rewrapped its text
|
|
16
|
+
|
|
3
17
|
## 0.49.0
|
|
4
18
|
|
|
5
19
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -130,6 +130,9 @@ runs where `@ahrowe/ui` is installed.
|
|
|
130
130
|
| `Calendar`, `CalendarView` | Showing and editing your own events over time, in month, week, day and agenda views: bookings, shifts, appointments |
|
|
131
131
|
| `Card`, `CardStyleType`, `CardHeader`, `CardMedia`, `CardContent`, `CardActions`, `ThemeColor` | Surface container for grouping related content — list items, profile cards, dashboard tiles, product listings |
|
|
132
132
|
| `Carousel` | A slide track for stepping through a set of items one at a time, with arrows, dots, and drag/swipe support, plus optional autoplay |
|
|
133
|
+
| `ChatComposer` | The field a chat message is written in, with its send button: under a ChatMessageList, which shows a whole chat put together |
|
|
134
|
+
| `ChatMessage`, `ChatMessageStyleType`, `ChatMessageStatus` | One message in a conversation: a messenger bubble, a support agent's reply, an AI assistant's answer |
|
|
135
|
+
| `ChatMessageList` | The scrolling conversation of a chat: a messenger, a support widget, an AI assistant |
|
|
133
136
|
| `Checkbox` | Boolean toggle input — settings, multi-select lists, accept terms, feature flags |
|
|
134
137
|
| `Chip`, `ChipContainer`, `ChipStyleType`, `ThemeColor` | Compact tag, label, or filter token — selected items, applied filters, category badges, user avatars with remove |
|
|
135
138
|
| `ColorPicker` | Hex colour selection — theme editors, annotation tools, customisation panels |
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { CSSProperties, ReactNode, Ref, TextareaHTMLAttributes } from 'react';
|
|
2
|
+
import { SlotClassNames, SlotStyles, DataProps } from '../types/slots.types.js';
|
|
3
|
+
export type ChatComposerSlots = 'input' | 'button';
|
|
4
|
+
/** Text ChatComposer renders of its own, for translating it. */
|
|
5
|
+
export interface ChatComposerLabels {
|
|
6
|
+
/** Shown in the empty field, and its accessible name. */
|
|
7
|
+
placeholder?: string;
|
|
8
|
+
/** Send button, read out by screen readers. */
|
|
9
|
+
send?: string;
|
|
10
|
+
/** Stop button shown while `onStop` is set, read out by screen readers. */
|
|
11
|
+
stop?: string;
|
|
12
|
+
}
|
|
13
|
+
/** Native attributes go to the `<textarea>`; `className` and `style` to the root. */
|
|
14
|
+
export interface ChatComposerProps extends DataProps, Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, 'onChange' | 'value' | 'defaultValue' | 'placeholder'> {
|
|
15
|
+
/** Called with the trimmed text. Not called while the field is empty or only whitespace. */
|
|
16
|
+
onSend: (text: string) => void;
|
|
17
|
+
/** Controlled value. Sending clears it through `onChange('')`. */
|
|
18
|
+
value?: string;
|
|
19
|
+
defaultValue?: string;
|
|
20
|
+
onChange?: (value: string) => void;
|
|
21
|
+
/** Runs before the composer's own handling; `preventDefault()` on an Enter keeps it from sending. */
|
|
22
|
+
onKeyDown?: TextareaHTMLAttributes<HTMLTextAreaElement>['onKeyDown'];
|
|
23
|
+
/** Swaps the send button for a stop button while set, e.g. while an AI answer streams. Enter sends nothing meanwhile. */
|
|
24
|
+
onStop?: () => void;
|
|
25
|
+
disabled?: boolean;
|
|
26
|
+
/** The height of the empty field, in lines. It grows from there to seven. Default 1. */
|
|
27
|
+
rows?: number;
|
|
28
|
+
/** Before the field: an attach or emoji button. Moves to a row below it once the text wraps. */
|
|
29
|
+
startContent?: ReactNode;
|
|
30
|
+
/** Between the field and the send button. Moves to a row below it, with the send button, once the text wraps. */
|
|
31
|
+
endContent?: ReactNode;
|
|
32
|
+
/** Ref to the inner `<textarea>`. */
|
|
33
|
+
ref?: Ref<HTMLTextAreaElement>;
|
|
34
|
+
/** Overrides the text ChatComposer renders of its own. Merged per key. */
|
|
35
|
+
labels?: ChatComposerLabels;
|
|
36
|
+
className?: string;
|
|
37
|
+
style?: CSSProperties;
|
|
38
|
+
classNames?: SlotClassNames<ChatComposerSlots>;
|
|
39
|
+
styles?: SlotStyles<ChatComposerSlots>;
|
|
40
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { CSSProperties, ReactNode } from 'react';
|
|
2
|
+
import { SlotClassNames, SlotStyles, HtmlProps } from '../types/slots.types.js';
|
|
3
|
+
import { ColorValue } from '../themeProvider/color.types.js';
|
|
4
|
+
/** How much the message stands out. Independent of its colour. */
|
|
5
|
+
export declare enum ChatMessageStyleType {
|
|
6
|
+
/** Solid bubble, text in a contrasting colour. The default for `isOwn`. */
|
|
7
|
+
Filled = "filled",
|
|
8
|
+
/** A light tint of the colour. The default for everyone else. */
|
|
9
|
+
Light = "light",
|
|
10
|
+
/** No bubble, full width: an AI assistant's answer. */
|
|
11
|
+
Plain = "plain"
|
|
12
|
+
}
|
|
13
|
+
/** Delivery state of an own message: an icon next to `time`, and `Failed` a notice under the bubble. */
|
|
14
|
+
export declare enum ChatMessageStatus {
|
|
15
|
+
Sending = "sending",
|
|
16
|
+
Sent = "sent",
|
|
17
|
+
Delivered = "delivered",
|
|
18
|
+
Read = "read",
|
|
19
|
+
Failed = "failed"
|
|
20
|
+
}
|
|
21
|
+
export type ChatMessageSlots = 'avatar' | 'body' | 'author' | 'bubble' | 'meta' | 'actions';
|
|
22
|
+
/** Text ChatMessage renders of its own, for translating it. */
|
|
23
|
+
export interface ChatMessageLabels {
|
|
24
|
+
/** Read out for the status icon. */
|
|
25
|
+
sending?: string;
|
|
26
|
+
sent?: string;
|
|
27
|
+
delivered?: string;
|
|
28
|
+
read?: string;
|
|
29
|
+
/** Shown under the bubble. */
|
|
30
|
+
failed?: string;
|
|
31
|
+
/** Names an own message without `author` for screen readers. */
|
|
32
|
+
you?: string;
|
|
33
|
+
}
|
|
34
|
+
export interface ChatMessageProps extends HtmlProps<HTMLDivElement> {
|
|
35
|
+
/** The message. A string keeps its line breaks; anything else (rendered Markdown, an image) renders as given. */
|
|
36
|
+
children?: ReactNode;
|
|
37
|
+
/** Sent by the current user: aligned to the end, `Filled` in `Primary` by default. */
|
|
38
|
+
isOwn?: boolean;
|
|
39
|
+
/** Follows a message from the same author: hides `author` from view, keeps the space of `avatar` and tightens the gap above. */
|
|
40
|
+
isContinued?: boolean;
|
|
41
|
+
/** Shown beside the bubble, typically an `Avatar`. */
|
|
42
|
+
avatar?: ReactNode;
|
|
43
|
+
/** Name above the bubble. Screen readers get it on every message of a run, not only the first. */
|
|
44
|
+
author?: ReactNode;
|
|
45
|
+
/** Shown in the bubble's corner. Format it yourself, e.g. with `Intl.DateTimeFormat`. */
|
|
46
|
+
time?: ReactNode;
|
|
47
|
+
status?: ChatMessageStatus;
|
|
48
|
+
/** Shown below the bubble, after the `Failed` notice: copy, retry, rate. */
|
|
49
|
+
actions?: ReactNode;
|
|
50
|
+
/** Default `Filled` when `isOwn`, otherwise `Light`. */
|
|
51
|
+
styleType?: ChatMessageStyleType;
|
|
52
|
+
/** A `ThemeColor` or any CSS colour. Default `Primary` when `isOwn`, otherwise `Neutral`. */
|
|
53
|
+
color?: ColorValue;
|
|
54
|
+
/** Overrides the text ChatMessage renders of its own. Merged per key. */
|
|
55
|
+
labels?: ChatMessageLabels;
|
|
56
|
+
className?: string;
|
|
57
|
+
style?: CSSProperties;
|
|
58
|
+
classNames?: SlotClassNames<ChatMessageSlots>;
|
|
59
|
+
styles?: SlotStyles<ChatMessageSlots>;
|
|
60
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { CSSProperties, ReactNode, Ref } from 'react';
|
|
2
|
+
import { SlotClassNames, SlotStyles, HtmlProps } from '../types/slots.types.js';
|
|
3
|
+
export type ChatMessageListSlots = 'viewport' | 'content' | 'scrollButton';
|
|
4
|
+
/** Text ChatMessageList renders of its own, for translating it. */
|
|
5
|
+
export interface ChatMessageListLabels {
|
|
6
|
+
/** Accessible name of the message log. */
|
|
7
|
+
messages?: string;
|
|
8
|
+
/** Button that jumps back to the newest message, read out by screen readers. */
|
|
9
|
+
scrollToBottom?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface ChatMessageListHandle {
|
|
12
|
+
/** Scrolls to the newest message and follows new ones again. Call it after the user sends one. */
|
|
13
|
+
scrollToBottom: () => void;
|
|
14
|
+
}
|
|
15
|
+
export interface ChatMessageListProps extends HtmlProps<HTMLDivElement> {
|
|
16
|
+
/** `ChatMessage`s, and anything between them: a `Divider` with a date, a typing indicator. Oldest first. */
|
|
17
|
+
children?: ReactNode;
|
|
18
|
+
/** Loads older messages when the user scrolls to the top. Prepend them; the view stays where it was. Leave it out once there are no more. */
|
|
19
|
+
onLoadMore?: () => Promise<void>;
|
|
20
|
+
ref?: Ref<ChatMessageListHandle>;
|
|
21
|
+
/** Overrides the text ChatMessageList renders of its own. Merged per key. */
|
|
22
|
+
labels?: ChatMessageListLabels;
|
|
23
|
+
className?: string;
|
|
24
|
+
style?: CSSProperties;
|
|
25
|
+
classNames?: SlotClassNames<ChatMessageListSlots>;
|
|
26
|
+
styles?: SlotStyles<ChatMessageListSlots>;
|
|
27
|
+
}
|
|
@@ -49,6 +49,9 @@ import { RoomViewerProps } from '../roomViewer/roomViewer.types.js';
|
|
|
49
49
|
import { PopoverProps } from '../popover/popover.types.js';
|
|
50
50
|
import { ModalProps } from '../modal/modal.types.js';
|
|
51
51
|
import { StepperProps } from '../stepper/stepper.types.js';
|
|
52
|
+
import { ChatMessageProps } from '../chatMessage/chatMessage.types.js';
|
|
53
|
+
import { ChatMessageListProps } from '../chatMessageList/chatMessageList.types.js';
|
|
54
|
+
import { ChatComposerProps } from '../chatComposer/chatComposer.types.js';
|
|
52
55
|
import { Presentation } from '../configProvider/presentation.types.js';
|
|
53
56
|
/**
|
|
54
57
|
* Global default props keyed by component name.
|
|
@@ -133,6 +136,9 @@ export interface ComponentDefaults {
|
|
|
133
136
|
CommandPalette?: Partial<CommandPaletteProps>;
|
|
134
137
|
WeatherIcon?: Partial<WeatherIconProps>;
|
|
135
138
|
Stepper?: Partial<StepperProps>;
|
|
139
|
+
ChatMessage?: Partial<ChatMessageProps>;
|
|
140
|
+
ChatMessageList?: Partial<ChatMessageListProps>;
|
|
141
|
+
ChatComposer?: Partial<ChatComposerProps>;
|
|
136
142
|
}
|
|
137
143
|
export interface ConfigProviderProps {
|
|
138
144
|
children: React.ReactNode;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { RefObject } from 'react';
|
|
2
|
+
interface AutoGrowOptions {
|
|
3
|
+
enabled?: boolean;
|
|
4
|
+
/** Whether the text needs more than the field's `rows`, after each change of `value` or width. */
|
|
5
|
+
onGrow?: (isGrown: boolean) => void;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Keeps a textarea as tall as its text, up to the `max-height` its stylesheet sets. It measures
|
|
9
|
+
* again when `value` changes, so the field must be controlled, and when its width changes.
|
|
10
|
+
*/
|
|
11
|
+
export declare function useAutoGrow(ref: RefObject<HTMLElement | null>, value: unknown, { enabled, onGrow }?: AutoGrowOptions): void;
|
|
12
|
+
export {};
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { default as React } from 'react';
|
|
2
2
|
import { IconContent } from '../types/icon.types.js';
|
|
3
3
|
import { SlotClassNames, SlotStyles, HtmlProps } from '../types/slots.types.js';
|
|
4
|
-
export type TabHeaderSlots = 'root' | 'tab' | 'icon' | 'labels' | 'title' | 'subtitle';
|
|
4
|
+
export type TabHeaderSlots = 'root' | 'tab' | 'icon' | 'labels' | 'title' | 'subtitle' | 'endContent';
|
|
5
5
|
export interface TabHeaderTab {
|
|
6
6
|
id: string;
|
|
7
7
|
icon: IconContent;
|
|
8
|
-
title?:
|
|
9
|
-
subtitle?:
|
|
8
|
+
title?: React.ReactNode;
|
|
9
|
+
subtitle?: React.ReactNode;
|
|
10
|
+
/** After the labels. Stays visible when a narrow screen collapses the tab, so keep it small: a `Badge`, a dot. */
|
|
11
|
+
endContent?: React.ReactNode;
|
|
10
12
|
disabled?: boolean;
|
|
11
13
|
}
|
|
12
14
|
export interface TabHeaderProps extends Omit<HtmlProps, 'onChange'> {
|
|
@@ -29,6 +29,12 @@ export { default as Card } from './common/card/index.js';
|
|
|
29
29
|
export * from './common/card/index.js';
|
|
30
30
|
export { default as Carousel } from './common/carousel/index.js';
|
|
31
31
|
export * from './common/carousel/index.js';
|
|
32
|
+
export { default as ChatComposer } from './common/chatComposer/index.js';
|
|
33
|
+
export * from './common/chatComposer/index.js';
|
|
34
|
+
export { default as ChatMessage } from './common/chatMessage/index.js';
|
|
35
|
+
export * from './common/chatMessage/index.js';
|
|
36
|
+
export { default as ChatMessageList } from './common/chatMessageList/index.js';
|
|
37
|
+
export * from './common/chatMessageList/index.js';
|
|
32
38
|
export { default as Checkbox } from './common/checkbox/index.js';
|
|
33
39
|
export * from './common/checkbox/index.js';
|
|
34
40
|
export { default as Chip } from './common/chip/index.js';
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";import{useComponentDefaults as e}from"../configProvider/useComponentDefaults.mjs";import{useLabels as t}from"../configProvider/useLabels.mjs";import{ThemeColor as n}from"../themeProvider/color.types.mjs";import{ButtonStyleType as r}from"../button/button.types.mjs";import i from"../actionIcon/actionIcon.mjs";import a from"../hooks/useCoarsePointer.mjs";import{mergeRefs as o}from"../utils/mergeRefs.mjs";import{useAutoGrow as s}from"../hooks/useAutoGrow.mjs";import c from"./chatComposer.module.mjs";import{useRef as l,useState as u}from"react";import d from"classnames";import{faPaperPlane as f,faStop as p}from"@fortawesome/free-solid-svg-icons";import{jsx as m,jsxs as h}from"react/jsx-runtime";var g={placeholder:`Message`,send:`Send`,stop:`Stop`};function _(_){let{onSend:v,value:y,defaultValue:b=``,onChange:x,onKeyDown:S,onStop:C,disabled:w=!1,readOnly:T=!1,rows:E=1,startContent:D,endContent:O,ref:k,labels:A,className:j,style:M,classNames:N,styles:P,...F}=e(`ChatComposer`,_),I=t(`ChatComposer`,A,g),L=a(),R=l(null),[z,B]=u(b),V=y??z,H=!w&&!T&&V.trim()!==``,[U,W]=u(!1);U&&V===``&&W(!1);let G=U&&(D!=null||O!=null);s(R,V,{onGrow:e=>{e&&R.current?.value&&W(!0)}});let K=e=>{y===void 0&&B(e),x?.(e)},q=()=>{H&&!C&&(v(V.trim()),K(``),R.current?.focus())},J=e=>{S?.(e),!(e.defaultPrevented||e.key!==`Enter`||e.shiftKey)&&(e.nativeEvent.isComposing||e.keyCode===229||(!L||e.metaKey||e.ctrlKey)&&(e.preventDefault(),q()))},Y=e=>e.preventDefault();return h(`div`,{className:d(c.chatComposer,G&&c.chatComposerStacked,w&&c.chatComposerDisabled,j),style:M,children:[D!=null&&m(`div`,{className:c.chatComposerSection,children:D}),m(`textarea`,{"aria-label":I.placeholder,...F,ref:o(R,k),rows:E,value:V,placeholder:I.placeholder,disabled:w,readOnly:T,className:d(c.chatComposerInput,N?.input),style:P?.input,onChange:e=>K(e.target.value),onKeyDown:J}),O!=null&&m(`div`,{className:c.chatComposerSection,children:O}),C?m(i,{icon:p,styleType:r.Filled,color:n.Primary,"aria-label":I.stop,title:I.stop,disabled:w,className:d(c.chatComposerButton,N?.button),style:P?.button,onMouseDown:Y,onClick:C}):m(i,{icon:f,styleType:r.Filled,color:n.Primary,"aria-label":I.send,title:I.send,disabled:!H,className:d(c.chatComposerButton,N?.button),style:P?.button,onMouseDown:Y,onClick:q})]})}export{_ as default};
|
|
2
|
+
//# sourceMappingURL=chatComposer.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chatComposer.mjs","names":[],"sources":["../../../../package/common/chatComposer/chatComposer.tsx"],"sourcesContent":["import { useRef, useState, type KeyboardEvent, type MouseEvent, type ReactElement } from 'react';\nimport cx from 'classnames';\nimport { faPaperPlane, faStop } from '@fortawesome/free-solid-svg-icons';\n\nimport ActionIcon from '../actionIcon';\nimport { ButtonStyleType } from '../button/button.types';\nimport { useComponentDefaults, useLabels } from '../configProvider';\nimport { useAutoGrow } from '../hooks/useAutoGrow';\nimport { useCoarsePointer } from '../hooks/useCoarsePointer';\nimport { ThemeColor } from '../themeProvider/color.types';\nimport { mergeRefs } from '../utils/mergeRefs';\nimport type { ChatComposerLabels, ChatComposerProps } from './chatComposer.types';\nimport styles from './chatComposer.module.pcss';\n\nconst DEFAULT_LABELS: Required<ChatComposerLabels> = {\n placeholder: 'Message',\n send: 'Send',\n stop: 'Stop',\n};\n\nfunction ChatComposer(props: ChatComposerProps): ReactElement {\n const {\n onSend,\n value,\n defaultValue = '',\n onChange,\n onKeyDown,\n onStop,\n disabled = false,\n readOnly = false,\n rows = 1,\n startContent,\n endContent,\n ref,\n labels,\n className,\n style,\n classNames,\n styles: slotStyles,\n ...rest\n } = useComponentDefaults('ChatComposer', props);\n const text = useLabels('ChatComposer', labels, DEFAULT_LABELS);\n const isCoarsePointer = useCoarsePointer();\n const inputRef = useRef<HTMLTextAreaElement>(null);\n const [internalValue, setInternalValue] = useState(defaultValue);\n const current = value ?? internalValue;\n const canSend = !disabled && !readOnly && current.trim() !== '';\n const [isGrown, setIsGrown] = useState(false);\n\n // Back to one row only once the field is empty. Below the buttons the field is wider, and text\n // that wrapped beside them may fit on one line there: dropping back then would wrap it again.\n if (isGrown && current === '') setIsGrown(false);\n // With nothing besides the send button, a row of its own would cost more height than it frees.\n const isStacked = isGrown && (startContent != null || endContent != null);\n\n useAutoGrow(inputRef, current, {\n onGrow: (grown) => {\n // An empty field can measure taller than a line: Chrome counts a wrapping placeholder.\n if (grown && inputRef.current?.value) setIsGrown(true);\n },\n });\n\n const setValue = (next: string) => {\n if (value === undefined) setInternalValue(next);\n onChange?.(next);\n };\n\n const send = () => {\n if (!canSend || onStop) return;\n onSend(current.trim());\n setValue('');\n inputRef.current?.focus();\n };\n\n const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {\n onKeyDown?.(event);\n // A consumer's own use of Enter comes first, such as picking from an open mention list.\n if (event.defaultPrevented || event.key !== 'Enter' || event.shiftKey) return;\n // The Enter that confirms a word in a Japanese, Chinese or Korean IME. Safari sends its keydown\n // after compositionend, with isComposing already false, and only keyCode 229 gives it away.\n if (event.nativeEvent.isComposing || event.keyCode === 229) return;\n // On a touch keyboard Enter is the only way to start a new line, so there the button sends.\n if (isCoarsePointer && !event.metaKey && !event.ctrlKey) return;\n event.preventDefault();\n send();\n };\n\n // Keeps the focus in the field when the button is pressed, so a phone keyboard stays open.\n const keepFocus = (event: MouseEvent) => event.preventDefault();\n\n return (\n <div\n className={cx(\n styles.chatComposer,\n isStacked && styles.chatComposerStacked,\n disabled && styles.chatComposerDisabled,\n className,\n )}\n style={style}\n >\n {startContent != null && <div className={styles.chatComposerSection}>{startContent}</div>}\n <textarea\n // Before the spread, so a name of the consumer's own wins over the placeholder.\n aria-label={text.placeholder}\n {...rest}\n ref={mergeRefs(inputRef, ref)}\n rows={rows}\n value={current}\n placeholder={text.placeholder}\n disabled={disabled}\n readOnly={readOnly}\n className={cx(styles.chatComposerInput, classNames?.input)}\n style={slotStyles?.input}\n onChange={(event) => setValue(event.target.value)}\n onKeyDown={handleKeyDown}\n />\n {endContent != null && <div className={styles.chatComposerSection}>{endContent}</div>}\n {onStop ? (\n <ActionIcon\n icon={faStop}\n styleType={ButtonStyleType.Filled}\n color={ThemeColor.Primary}\n aria-label={text.stop}\n title={text.stop}\n disabled={disabled}\n className={cx(styles.chatComposerButton, classNames?.button)}\n style={slotStyles?.button}\n onMouseDown={keepFocus}\n onClick={onStop}\n />\n ) : (\n <ActionIcon\n icon={faPaperPlane}\n styleType={ButtonStyleType.Filled}\n color={ThemeColor.Primary}\n aria-label={text.send}\n title={text.send}\n disabled={!canSend}\n className={cx(styles.chatComposerButton, classNames?.button)}\n style={slotStyles?.button}\n onMouseDown={keepFocus}\n onClick={send}\n />\n )}\n </div>\n );\n}\n\nexport default ChatComposer;\n"],"mappings":"wsBAcA,IAAM,EAA+C,CACnD,YAAa,UACb,KAAM,OACN,KAAM,MACR,EAEA,SAAS,EAAa,EAAwC,CAC5D,GAAM,CACJ,SACA,QACA,eAAe,GACf,WACA,YACA,SACA,WAAW,GACX,WAAW,GACX,OAAO,EACP,eACA,aACA,MACA,SACA,YACA,QACA,aACA,OAAQ,EACR,GAAG,GACD,EAAqB,eAAgB,CAAK,EACxC,EAAO,EAAU,eAAgB,EAAQ,CAAc,EACvD,EAAkB,EAAiB,EACnC,EAAW,EAA4B,IAAI,EAC3C,CAAC,EAAe,GAAoB,EAAS,CAAY,EACzD,EAAU,GAAS,EACnB,EAAU,CAAC,GAAY,CAAC,GAAY,EAAQ,KAAK,IAAM,GACvD,CAAC,EAAS,GAAc,EAAS,EAAK,EAIxC,GAAW,IAAY,IAAI,EAAW,EAAK,EAE/C,IAAM,EAAY,IAAY,GAAgB,MAAQ,GAAc,MAEpE,EAAY,EAAU,EAAS,CAC7B,OAAS,GAAU,CAEb,GAAS,EAAS,SAAS,OAAO,EAAW,EAAI,CACvD,CACF,CAAC,EAED,IAAM,EAAY,GAAiB,CAC7B,IAAU,IAAA,IAAW,EAAiB,CAAI,EAC9C,IAAW,CAAI,CACjB,EAEM,MAAa,CACZ,GAAW,KAChB,EAAO,EAAQ,KAAK,CAAC,EACrB,EAAS,EAAE,EACX,EAAS,SAAS,MAAM,EAC1B,EAEM,EAAiB,GAA8C,CACnE,IAAY,CAAK,EAEb,IAAM,kBAAoB,EAAM,MAAQ,SAAW,EAAM,YAGzD,EAAM,YAAY,aAAe,EAAM,UAAY,MAEnD,IAAoB,EAAM,SAAY,EAAM,WAChD,EAAM,eAAe,EACrB,EAAK,GACP,EAGM,EAAa,GAAsB,EAAM,eAAe,EAE9D,OACE,EAAC,MAAD,CACE,UAAW,EACT,EAAO,aACP,GAAa,EAAO,oBACpB,GAAY,EAAO,qBACnB,CACF,EACO,QAPT,SAAA,CASG,GAAgB,MAAQ,EAAC,MAAD,CAAK,UAAW,EAAO,oBAAsB,SAAA,CAAkB,CAAA,EACxF,EAAC,WAAD,CAEE,aAAY,EAAK,YACjB,GAAI,EACJ,IAAK,EAAU,EAAU,CAAG,EACtB,OACN,MAAO,EACP,YAAa,EAAK,YACR,WACA,WACV,UAAW,EAAG,EAAO,kBAAmB,GAAY,KAAK,EACzD,MAAO,GAAY,MACnB,SAAW,GAAU,EAAS,EAAM,OAAO,KAAK,EAChD,UAAW,CACZ,CAAA,EACA,GAAc,MAAQ,EAAC,MAAD,CAAK,UAAW,EAAO,oBAAsB,SAAA,CAAgB,CAAA,EACnF,EACC,EAAC,EAAD,CACE,KAAM,EACN,UAAW,EAAgB,OAC3B,MAAO,EAAW,QAClB,aAAY,EAAK,KACjB,MAAO,EAAK,KACF,WACV,UAAW,EAAG,EAAO,mBAAoB,GAAY,MAAM,EAC3D,MAAO,GAAY,OACnB,YAAa,EACb,QAAS,CACV,CAAA,EAED,EAAC,EAAD,CACE,KAAM,EACN,UAAW,EAAgB,OAC3B,MAAO,EAAW,QAClB,aAAY,EAAK,KACjB,MAAO,EAAK,KACZ,SAAU,CAAC,EACX,UAAW,EAAG,EAAO,mBAAoB,GAAY,MAAM,EAC3D,MAAO,GAAY,OACnB,YAAa,EACb,QAAS,CACV,CAAA,CAEA,GAET"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";var e=`chatComposer_JUIOk`,t=`chatComposer-stacked_At2fJ`,n=`chatComposer-disabled_S2nwO`,r=`chatComposer-section_-H4W4`,i=`chatComposer-input_eCZ2d`,a=`chatComposer-button_O6siA`,o={chatComposer:e,"chatComposer-stacked":`chatComposer-stacked_At2fJ`,chatComposerStacked:t,"chatComposer-disabled":`chatComposer-disabled_S2nwO`,chatComposerDisabled:n,"chatComposer-section":`chatComposer-section_-H4W4`,chatComposerSection:r,"chatComposer-input":`chatComposer-input_eCZ2d`,chatComposerInput:i,"chatComposer-button":`chatComposer-button_O6siA`,chatComposerButton:a};export{e as chatComposer,a as chatComposerButton,n as chatComposerDisabled,i as chatComposerInput,r as chatComposerSection,t as chatComposerStacked,o as default};
|
|
2
|
+
//# sourceMappingURL=chatComposer.module.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chatComposer.module.mjs","names":[],"sources":["../../../../package/common/chatComposer/chatComposer.module.pcss"],"sourcesContent":[".chatComposer {\n display: flex;\n align-items: flex-end;\n gap: 0.25em;\n box-sizing: border-box;\n padding: 0.375em;\n border: 1px solid var(--border-color);\n border-radius: calc(var(--default-border-radius) * 1.5);\n background: var(--input-background);\n color: var(--text-color);\n\n &:focus-within {\n border-color: var(--focus-border-color, var(--primary-color));\n }\n\n /* Once the text needs a second line, the field takes the whole width and the buttons a row\n below it: beside it, a few of them would leave it too narrow to write in. Only the order\n changes, never the element, so the focus and an IME composition survive the switch. */\n &-stacked {\n flex-wrap: wrap;\n }\n\n &-disabled {\n background-color: var(--disabled-input-color);\n }\n\n /* As tall as one line of the field, so a smaller button centres on the first line rather than\n sitting on the field's bottom edge. */\n &-section {\n display: flex;\n flex: none;\n align-items: center;\n gap: 0.25em;\n min-height: 2.25em;\n }\n\n &-input {\n flex: 1 1 auto;\n min-width: 0;\n box-sizing: border-box;\n\n /* Seven lines of line-height 1.5, plus the padding. */\n max-height: 11.25em;\n margin: 0;\n padding: 0.375em 0.5em;\n border: 0;\n outline: none;\n background: transparent;\n color: inherit;\n font: inherit;\n line-height: 1.5;\n resize: none;\n overflow-y: auto;\n\n &::placeholder {\n color: var(--text-dark);\n }\n\n &:disabled {\n color: var(--disabled-font-color);\n -webkit-text-fill-color: var(--disabled-font-color);\n }\n }\n}\n\n.chatComposer-stacked .chatComposer-input {\n flex-basis: 100%;\n order: -1;\n}\n\n/* The end content, or the send button when there is none, and with it everything after: to the\n far end of the row below. Two classes, as it can be the ActionIcon. */\n.chatComposer-stacked .chatComposer-input + * {\n margin-inline-start: auto;\n}\n\n/* Two classes: it is an ActionIcon, whose own one-class rules it would otherwise tie with. As tall\n as one line of the field, line-height plus padding, so the two line up while it is empty. */\n.chatComposer .chatComposer-button {\n flex: none;\n width: 2.25em;\n height: 2.25em;\n border-radius: 50%;\n}\n"],"mappings":""}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";import{useComponentDefaults as e}from"../configProvider/useComponentDefaults.mjs";import{useLabels as t}from"../configProvider/useLabels.mjs";import{ThemeColor as n}from"../themeProvider/color.types.mjs";import{toneProps as r,useToneCheck as i}from"../styles/tone.mjs";import{ChatMessageStatus as a,ChatMessageStyleType as o}from"./chatMessage.types.mjs";import s from"./chatMessage.module.mjs";import c from"classnames";import{FontAwesomeIcon as l}from"@fortawesome/react-fontawesome";import{faCheck as u,faCheckDouble as d,faCircleExclamation as f,faClock as p}from"@fortawesome/free-solid-svg-icons";import{jsx as m,jsxs as h}from"react/jsx-runtime";var g={sending:`Sending`,sent:`Sent`,delivered:`Delivered`,read:`Read`,failed:`Not sent`,you:`You`},_={[a.Sending]:p,[a.Sent]:u,[a.Delivered]:d,[a.Read]:d},v={[o.Filled]:s.chatMessageBubbleFilled,[o.Light]:s.chatMessageBubbleLight,[o.Plain]:s.chatMessageBubblePlain},y=e=>e!=null&&typeof e!=`boolean`&&e!==``;function b(u){let{children:d,isOwn:p=!1,isContinued:b=!1,avatar:x,author:S,time:C,status:w,actions:T,styleType:E=p?o.Filled:o.Light,color:D=p?n.Primary:n.Neutral,labels:O,className:k,style:A,classNames:j,styles:M,...N}=e(`ChatMessage`,u),P=t(`ChatMessage`,O,g),F=r(D);i(`ChatMessage`,[D]);let I=w===a.Failed,L=w==null||I?null:_[w],R=y(S)?S:p?P.you:null;return h(`div`,{...N,className:c(s.chatMessage,p&&s.chatMessageOwn,b&&s.chatMessageContinued,E===o.Plain&&s.chatMessagePlain,k),style:A,children:[y(x)&&m(`div`,{className:c(s.chatMessageAvatar,j?.avatar),style:M?.avatar,children:x}),h(`div`,{className:c(s.chatMessageBody,j?.body),style:M?.body,children:[R!=null&&m(`div`,{className:c(s.chatMessageAuthor,(b||!y(S))&&s.chatMessageAuthorHidden,j?.author),style:M?.author,children:R}),h(`div`,{className:c(s.chatMessageBubble,F.className,v[E],j?.bubble),style:{...F.style,...M?.bubble},children:[m(`div`,{className:c(s.chatMessageContent,typeof d==`string`&&s.chatMessageContentText),children:d}),(y(C)||L)&&h(`span`,{className:c(s.chatMessageMeta,j?.meta),style:M?.meta,children:[C,w!=null&&L&&m(`span`,{role:`img`,"aria-label":P[w],className:c(s.chatMessageStatus,w===a.Read&&s.chatMessageStatusRead),children:m(l,{icon:L})})]})]}),(I||y(T))&&h(`div`,{className:c(s.chatMessageActions,j?.actions),style:M?.actions,children:[I&&h(`span`,{className:s.chatMessageFailed,children:[m(l,{icon:f,className:s.chatMessageFailedIcon}),P.failed]}),T]})]})]})}export{b as default};
|
|
2
|
+
//# sourceMappingURL=chatMessage.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chatMessage.mjs","names":[],"sources":["../../../../package/common/chatMessage/chatMessage.tsx"],"sourcesContent":["import type { ReactElement, ReactNode } from 'react';\nimport cx from 'classnames';\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\nimport { faCheck, faCheckDouble, faCircleExclamation, faClock } from '@fortawesome/free-solid-svg-icons';\nimport type { IconDefinition } from '@fortawesome/fontawesome-svg-core';\n\nimport { useComponentDefaults, useLabels } from '../configProvider';\nimport { ThemeColor } from '../themeProvider/color.types';\nimport { toneProps, useToneCheck } from '../styles/tone';\nimport { ChatMessageStatus, ChatMessageStyleType } from './chatMessage.types';\nimport type { ChatMessageLabels, ChatMessageProps } from './chatMessage.types';\nimport styles from './chatMessage.module.pcss';\n\nconst DEFAULT_LABELS: Required<ChatMessageLabels> = {\n sending: 'Sending',\n sent: 'Sent',\n delivered: 'Delivered',\n read: 'Read',\n failed: 'Not sent',\n you: 'You',\n};\n\n// Failed has no icon in the bubble: it is a notice under it, where it has the page's background\n// to stand out against rather than the bubble's own colour.\nconst ICON_BY_STATUS: Record<Exclude<ChatMessageStatus, ChatMessageStatus.Failed>, IconDefinition> = {\n [ChatMessageStatus.Sending]: faClock,\n [ChatMessageStatus.Sent]: faCheck,\n [ChatMessageStatus.Delivered]: faCheckDouble,\n [ChatMessageStatus.Read]: faCheckDouble,\n};\n\nconst CLASS_BY_STYLE: Record<ChatMessageStyleType, string> = {\n [ChatMessageStyleType.Filled]: styles.chatMessageBubbleFilled,\n [ChatMessageStyleType.Light]: styles.chatMessageBubbleLight,\n [ChatMessageStyleType.Plain]: styles.chatMessageBubblePlain,\n};\n\n// So `actions={isFailed && <RetryButton />}` leaves no empty row behind when it is false.\nconst isShown = (node: ReactNode) => node != null && typeof node !== 'boolean' && node !== '';\n\nfunction ChatMessage(props: ChatMessageProps): ReactElement {\n const {\n children,\n isOwn = false,\n isContinued = false,\n avatar,\n author,\n time,\n status,\n actions,\n styleType = isOwn ? ChatMessageStyleType.Filled : ChatMessageStyleType.Light,\n color = isOwn ? ThemeColor.Primary : ThemeColor.Neutral,\n labels,\n className,\n style,\n classNames,\n styles: slotStyles,\n ...rest\n } = useComponentDefaults('ChatMessage', props);\n const text = useLabels('ChatMessage', labels, DEFAULT_LABELS);\n const tone = toneProps(color);\n useToneCheck('ChatMessage', [color]);\n const isFailed = status === ChatMessageStatus.Failed;\n const statusIcon = status == null || isFailed ? null : ICON_BY_STATUS[status];\n // Alignment is what tells an own message apart, and that is not read out.\n const sender = isShown(author) ? author : isOwn ? text.you : null;\n\n return (\n <div\n {...rest}\n className={cx(\n styles.chatMessage,\n isOwn && styles.chatMessageOwn,\n isContinued && styles.chatMessageContinued,\n styleType === ChatMessageStyleType.Plain && styles.chatMessagePlain,\n className,\n )}\n style={style}\n >\n {isShown(avatar) && (\n <div className={cx(styles.chatMessageAvatar, classNames?.avatar)} style={slotStyles?.avatar}>\n {avatar}\n </div>\n )}\n <div className={cx(styles.chatMessageBody, classNames?.body)} style={slotStyles?.body}>\n {sender != null && (\n <div\n className={cx(\n styles.chatMessageAuthor,\n (isContinued || !isShown(author)) && styles.chatMessageAuthorHidden,\n classNames?.author,\n )}\n style={slotStyles?.author}\n >\n {sender}\n </div>\n )}\n <div\n className={cx(styles.chatMessageBubble, tone.className, CLASS_BY_STYLE[styleType], classNames?.bubble)}\n style={{ ...tone.style, ...slotStyles?.bubble }}\n >\n <div className={cx(styles.chatMessageContent, typeof children === 'string' && styles.chatMessageContentText)}>\n {children}\n </div>\n {(isShown(time) || statusIcon) && (\n <span className={cx(styles.chatMessageMeta, classNames?.meta)} style={slotStyles?.meta}>\n {time}\n {status != null && statusIcon && (\n <span\n role='img'\n aria-label={text[status]}\n className={cx(\n styles.chatMessageStatus,\n status === ChatMessageStatus.Read && styles.chatMessageStatusRead,\n )}\n >\n <FontAwesomeIcon icon={statusIcon} />\n </span>\n )}\n </span>\n )}\n </div>\n {(isFailed || isShown(actions)) && (\n <div className={cx(styles.chatMessageActions, classNames?.actions)} style={slotStyles?.actions}>\n {isFailed && (\n <span className={styles.chatMessageFailed}>\n <FontAwesomeIcon icon={faCircleExclamation} className={styles.chatMessageFailedIcon} />\n {text.failed}\n </span>\n )}\n {actions}\n </div>\n )}\n </div>\n </div>\n );\n}\n\nexport default ChatMessage;\n"],"mappings":"0pBAaA,IAAM,EAA8C,CAClD,QAAS,UACT,KAAM,OACN,UAAW,YACX,KAAM,OACN,OAAQ,WACR,IAAK,KACP,EAIM,EAA+F,EAClG,EAAkB,SAAU,GAC5B,EAAkB,MAAO,GACzB,EAAkB,WAAY,GAC9B,EAAkB,MAAO,CAC5B,EAEM,EAAuD,EAC1D,EAAqB,QAAS,EAAO,yBACrC,EAAqB,OAAQ,EAAO,wBACpC,EAAqB,OAAQ,EAAO,sBACvC,EAGM,EAAW,GAAoB,GAAQ,MAAQ,OAAO,GAAS,WAAa,IAAS,GAE3F,SAAS,EAAY,EAAuC,CAC1D,GAAM,CACJ,WACA,QAAQ,GACR,cAAc,GACd,SACA,SACA,OACA,SACA,UACA,YAAY,EAAQ,EAAqB,OAAS,EAAqB,MACvE,QAAQ,EAAQ,EAAW,QAAU,EAAW,QAChD,SACA,YACA,QACA,aACA,OAAQ,EACR,GAAG,GACD,EAAqB,cAAe,CAAK,EACvC,EAAO,EAAU,cAAe,EAAQ,CAAc,EACtD,EAAO,EAAU,CAAK,EAC5B,EAAa,cAAe,CAAC,CAAK,CAAC,EACnC,IAAM,EAAW,IAAW,EAAkB,OACxC,EAAa,GAAU,MAAQ,EAAW,KAAO,EAAe,GAEhE,EAAS,EAAQ,CAAM,EAAI,EAAS,EAAQ,EAAK,IAAM,KAE7D,OACE,EAAC,MAAD,CACE,GAAI,EACJ,UAAW,EACT,EAAO,YACP,GAAS,EAAO,eAChB,GAAe,EAAO,qBACtB,IAAc,EAAqB,OAAS,EAAO,iBACnD,CACF,EACO,QATT,SAAA,CAWG,EAAQ,CAAM,GACb,EAAC,MAAD,CAAK,UAAW,EAAG,EAAO,kBAAmB,GAAY,MAAM,EAAG,MAAO,GAAY,OAClF,SAAA,CACE,CAAA,EAEP,EAAC,MAAD,CAAK,UAAW,EAAG,EAAO,gBAAiB,GAAY,IAAI,EAAG,MAAO,GAAY,KAAjF,SAAA,CACG,GAAU,MACT,EAAC,MAAD,CACE,UAAW,EACT,EAAO,mBACN,GAAe,CAAC,EAAQ,CAAM,IAAM,EAAO,wBAC5C,GAAY,MACd,EACA,MAAO,GAAY,OAElB,SAAA,CACE,CAAA,EAEP,EAAC,MAAD,CACE,UAAW,EAAG,EAAO,kBAAmB,EAAK,UAAW,EAAe,GAAY,GAAY,MAAM,EACrG,MAAO,CAAE,GAAG,EAAK,MAAO,GAAG,GAAY,MAAO,EAFhD,SAAA,CAIE,EAAC,MAAD,CAAK,UAAW,EAAG,EAAO,mBAAoB,OAAO,GAAa,UAAY,EAAO,sBAAsB,EACxG,UACE,CAAA,GACH,EAAQ,CAAI,GAAK,IACjB,EAAC,OAAD,CAAM,UAAW,EAAG,EAAO,gBAAiB,GAAY,IAAI,EAAG,MAAO,GAAY,KAAlF,SAAA,CACG,EACA,GAAU,MAAQ,GACjB,EAAC,OAAD,CACE,KAAK,MACL,aAAY,EAAK,GACjB,UAAW,EACT,EAAO,kBACP,IAAW,EAAkB,MAAQ,EAAO,qBAC9C,EAEA,SAAA,EAAC,EAAD,CAAiB,KAAM,CAAa,CAAA,CAChC,CAAA,CAEJ,CAEL,CAAA,CAAA,KACH,GAAY,EAAQ,CAAO,IAC3B,EAAC,MAAD,CAAK,UAAW,EAAG,EAAO,mBAAoB,GAAY,OAAO,EAAG,MAAO,GAAY,QAAvF,SAAA,CACG,GACC,EAAC,OAAD,CAAM,UAAW,EAAO,kBAAxB,SAAA,CACE,EAAC,EAAD,CAAiB,KAAM,EAAqB,UAAW,EAAO,qBAAwB,CAAA,EACrF,EAAK,MACF,CAEP,CAAA,EAAA,CACE,GAEJ,CACF,CAAA,CAAA,GAET"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";var e=`chatMessage_Q23vT`,t=`chatMessage-own_R-kPP`,n=`chatMessage-continued_NdGHe`,r=`chatMessage-avatar_KIgAU`,i=`chatMessage-body_h4IE4`,a=`chatMessage-author_TTIka`,o=`chatMessage-author-hidden_UdBUe`,s=`chatMessage-bubble_sngY-`,c=`chatMessage-bubble-filled_lJPjg`,l=`chatMessage-bubble-light_B136D`,u=`chatMessage-bubble-plain_Piy3I`,d=`chatMessage-content_ZlKd3`,f=`chatMessage-content-text_Yu8Xq`,p=`chatMessage-meta_mZXpW`,m=`chatMessage-status_JKUn1`,h=`chatMessage-status-read_tIIiI`,g=`chatMessage-actions_fDFS2`,_=`chatMessage-failed_855q3`,v=`chatMessage-failed-icon_d-5hO`,y=`chatMessage-plain_DCwiq`,b={chatMessage:e,"chatMessage-own":`chatMessage-own_R-kPP`,chatMessageOwn:t,"chatMessage-continued":`chatMessage-continued_NdGHe`,chatMessageContinued:n,"chatMessage-avatar":`chatMessage-avatar_KIgAU`,chatMessageAvatar:r,"chatMessage-body":`chatMessage-body_h4IE4`,chatMessageBody:i,"chatMessage-author":`chatMessage-author_TTIka`,chatMessageAuthor:a,"chatMessage-author-hidden":`chatMessage-author-hidden_UdBUe`,chatMessageAuthorHidden:o,"chatMessage-bubble":`chatMessage-bubble_sngY-`,chatMessageBubble:s,"chatMessage-bubble-filled":`chatMessage-bubble-filled_lJPjg`,chatMessageBubbleFilled:c,"chatMessage-bubble-light":`chatMessage-bubble-light_B136D`,chatMessageBubbleLight:l,"chatMessage-bubble-plain":`chatMessage-bubble-plain_Piy3I`,chatMessageBubblePlain:u,"chatMessage-content":`chatMessage-content_ZlKd3`,chatMessageContent:d,"chatMessage-content-text":`chatMessage-content-text_Yu8Xq`,chatMessageContentText:f,"chatMessage-meta":`chatMessage-meta_mZXpW`,chatMessageMeta:p,"chatMessage-status":`chatMessage-status_JKUn1`,chatMessageStatus:m,"chatMessage-status-read":`chatMessage-status-read_tIIiI`,chatMessageStatusRead:h,"chatMessage-actions":`chatMessage-actions_fDFS2`,chatMessageActions:g,"chatMessage-failed":`chatMessage-failed_855q3`,chatMessageFailed:_,"chatMessage-failed-icon":`chatMessage-failed-icon_d-5hO`,chatMessageFailedIcon:v,"chatMessage-plain":`chatMessage-plain_DCwiq`,chatMessagePlain:y};export{e as chatMessage,g as chatMessageActions,a as chatMessageAuthor,o as chatMessageAuthorHidden,r as chatMessageAvatar,i as chatMessageBody,s as chatMessageBubble,c as chatMessageBubbleFilled,l as chatMessageBubbleLight,u as chatMessageBubblePlain,d as chatMessageContent,f as chatMessageContentText,n as chatMessageContinued,_ as chatMessageFailed,v as chatMessageFailedIcon,p as chatMessageMeta,t as chatMessageOwn,y as chatMessagePlain,m as chatMessageStatus,h as chatMessageStatusRead,b as default};
|
|
2
|
+
//# sourceMappingURL=chatMessage.module.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chatMessage.module.mjs","names":[],"sources":["../../../../package/common/chatMessage/chatMessage.module.pcss"],"sourcesContent":[".chatMessage {\n position: relative;\n display: flex;\n align-items: flex-start;\n gap: 0.5em;\n margin-top: 0.75em;\n\n &:first-child {\n margin-top: 0;\n }\n\n &-own {\n flex-direction: row-reverse;\n }\n\n &-continued {\n margin-top: 0.125em;\n }\n\n &-avatar {\n flex: none;\n display: flex;\n }\n\n &-body {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n min-width: 0;\n max-width: min(80%, 40em);\n }\n\n &-author {\n margin-bottom: 0.25em;\n padding-inline: 0.75em;\n color: var(--text-dark);\n font-size: var(--text-small-font-size);\n font-weight: 600;\n\n /* Out of view but read out. Pinned to the message, or its containing block could be the page\n and leave empty space to scroll through below it. */\n &-hidden {\n position: absolute;\n top: 0;\n left: 0;\n width: 1px;\n height: 1px;\n margin: 0;\n padding: 0;\n clip-path: inset(50%);\n overflow: hidden;\n white-space: nowrap;\n }\n }\n\n /* Wraps rather than stacks: a short message keeps its meta on the same line, a long one pushes\n it onto a line of its own at the end. */\n &-bubble {\n display: flex;\n flex-wrap: wrap;\n align-items: flex-end;\n column-gap: 0.5em;\n box-sizing: border-box;\n max-width: 100%;\n padding: 0.5em 0.75em;\n border-radius: calc(var(--default-border-radius) * 1.5);\n border-start-start-radius: 0.25em;\n overflow-wrap: anywhere;\n\n &-filled {\n background: var(--tone-fill);\n\n /* Must follow the shorthand above, which resets background-image. */\n background-image: var(--tone-gradient);\n color: var(--tone-contrast);\n }\n\n /* Text keeps the page colour, as on a Light Card: a paragraph in the tone would not read. */\n &-light {\n background: var(--tone-light);\n color: var(--text-color);\n }\n\n &-plain {\n padding: 0;\n border-radius: 0;\n color: var(--text-color);\n }\n }\n\n &-content {\n flex: 1 1 auto;\n min-width: 0;\n\n &-text {\n white-space: pre-wrap;\n }\n }\n\n &-meta {\n display: inline-flex;\n align-items: center;\n gap: 0.25em;\n margin-inline-start: auto;\n color: color-mix(in srgb, currentcolor 70%, transparent);\n font-size: 0.75em;\n white-space: nowrap;\n }\n\n /* Read stands out by brightness as well as colour, which alone would not reach everyone, and on\n a filled bubble in a blue theme reaches no one. */\n &-status {\n display: inline-flex;\n opacity: 0.7;\n\n &-read {\n color: var(--info-color);\n opacity: 1;\n }\n }\n\n &-actions {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 0.25em;\n margin-top: 0.25em;\n }\n\n /* The text in the page's own colour: the default dark theme's red reaches 2.1:1 on its\n background, too little to read. The text carries the meaning, the red icon the signal. */\n &-failed {\n display: inline-flex;\n align-items: center;\n gap: 0.375em;\n padding-inline: 0.25em;\n color: var(--text-color);\n font-size: var(--text-small-font-size);\n\n &-icon {\n color: var(--error-color);\n }\n }\n}\n\n.chatMessage-own .chatMessage-body {\n align-items: flex-end;\n}\n\n.chatMessage-own .chatMessage-bubble {\n border-start-start-radius: calc(var(--default-border-radius) * 1.5);\n border-start-end-radius: 0.25em;\n}\n\n/* The corner pointing at the author marks the first message of a run, so the rest round it off.\n After the own rule, which it ties with. */\n.chatMessage-continued .chatMessage-bubble {\n border-start-start-radius: calc(var(--default-border-radius) * 1.5);\n border-start-end-radius: calc(var(--default-border-radius) * 1.5);\n}\n\n/* Keeps the column, so a run of messages stays aligned under the avatar of its first. */\n.chatMessage-continued .chatMessage-avatar {\n visibility: hidden;\n}\n\n.chatMessage-plain .chatMessage-body {\n flex: 1 1 auto;\n align-items: stretch;\n max-width: none;\n}\n\n.chatMessage-plain .chatMessage-author {\n padding-inline: 0;\n}\n\n/* The info blue would sink into a filled bubble. */\n.chatMessage-bubble-filled .chatMessage-status-read {\n color: var(--tone-contrast);\n}\n"],"mappings":""}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";var e=function(e){return e.Filled=`filled`,e.Light=`light`,e.Plain=`plain`,e}({}),t=function(e){return e.Sending=`sending`,e.Sent=`sent`,e.Delivered=`delivered`,e.Read=`read`,e.Failed=`failed`,e}({});export{t as ChatMessageStatus,e as ChatMessageStyleType};
|
|
2
|
+
//# sourceMappingURL=chatMessage.types.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chatMessage.types.mjs","names":[],"sources":["../../../../package/common/chatMessage/chatMessage.types.ts"],"sourcesContent":["import type { CSSProperties, ReactNode } from 'react';\nimport type { SlotClassNames, SlotStyles, HtmlProps } from '../types/slots.types';\nimport type { ColorValue } from '../themeProvider/color.types';\n\n/** How much the message stands out. Independent of its colour. */\nexport enum ChatMessageStyleType {\n /** Solid bubble, text in a contrasting colour. The default for `isOwn`. */\n Filled = 'filled',\n /** A light tint of the colour. The default for everyone else. */\n Light = 'light',\n /** No bubble, full width: an AI assistant's answer. */\n Plain = 'plain',\n}\n\n/** Delivery state of an own message: an icon next to `time`, and `Failed` a notice under the bubble. */\nexport enum ChatMessageStatus {\n Sending = 'sending',\n Sent = 'sent',\n Delivered = 'delivered',\n Read = 'read',\n Failed = 'failed',\n}\n\nexport type ChatMessageSlots = 'avatar' | 'body' | 'author' | 'bubble' | 'meta' | 'actions';\n\n/** Text ChatMessage renders of its own, for translating it. */\nexport interface ChatMessageLabels {\n /** Read out for the status icon. */\n sending?: string;\n sent?: string;\n delivered?: string;\n read?: string;\n /** Shown under the bubble. */\n failed?: string;\n /** Names an own message without `author` for screen readers. */\n you?: string;\n}\n\nexport interface ChatMessageProps extends HtmlProps<HTMLDivElement> {\n /** The message. A string keeps its line breaks; anything else (rendered Markdown, an image) renders as given. */\n children?: ReactNode;\n /** Sent by the current user: aligned to the end, `Filled` in `Primary` by default. */\n isOwn?: boolean;\n /** Follows a message from the same author: hides `author` from view, keeps the space of `avatar` and tightens the gap above. */\n isContinued?: boolean;\n /** Shown beside the bubble, typically an `Avatar`. */\n avatar?: ReactNode;\n /** Name above the bubble. Screen readers get it on every message of a run, not only the first. */\n author?: ReactNode;\n /** Shown in the bubble's corner. Format it yourself, e.g. with `Intl.DateTimeFormat`. */\n time?: ReactNode;\n status?: ChatMessageStatus;\n /** Shown below the bubble, after the `Failed` notice: copy, retry, rate. */\n actions?: ReactNode;\n /** Default `Filled` when `isOwn`, otherwise `Light`. */\n styleType?: ChatMessageStyleType;\n /** A `ThemeColor` or any CSS colour. Default `Primary` when `isOwn`, otherwise `Neutral`. */\n color?: ColorValue;\n /** Overrides the text ChatMessage renders of its own. Merged per key. */\n labels?: ChatMessageLabels;\n className?: string;\n style?: CSSProperties;\n classNames?: SlotClassNames<ChatMessageSlots>;\n styles?: SlotStyles<ChatMessageSlots>;\n}\n"],"mappings":"aAKA,IAAY,EAAL,SAAA,EAAA,OAEL,GAAA,OAAA,SAEA,EAAA,MAAA,QAEA,EAAA,MAAA,SACF,EAAA,CAAA,CAAA,EAGY,EAAL,SAAA,EAAA,OACL,GAAA,QAAA,UACA,EAAA,KAAA,OACA,EAAA,UAAA,YACA,EAAA,KAAA,OACA,EAAA,OAAA,UACF,EAAA,CAAA,CAAA"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";import{useComponentDefaults as e}from"../configProvider/useComponentDefaults.mjs";import{useLabels as t}from"../configProvider/useLabels.mjs";import{ThemeColor as n}from"../themeProvider/color.types.mjs";import r from"../loader/loader.mjs";import{ButtonStyleType as i}from"../button/button.types.mjs";import a from"../actionIcon/actionIcon.mjs";import{useInfiniteScroll as o}from"../infiniteBlock/useInfiniteScroll.mjs";import s from"./chatMessageList.module.mjs";import{useCallback as c,useImperativeHandle as l,useLayoutEffect as u,useRef as d,useState as f}from"react";import p from"classnames";import{faArrowDown as m}from"@fortawesome/free-solid-svg-icons";import{jsx as h,jsxs as g}from"react/jsx-runtime";var _={messages:`Messages`,scrollToBottom:`Jump to latest`},v=32,y=()=>Promise.resolve();function b(e,t,n){let r=e.getBoundingClientRect().top,i=t.children,a=+(i[0]===n),o=i.length-1,s=null;for(;a<=o;){let e=a+o>>1;i[e].getBoundingClientRect().bottom>r?(s=i[e],o=e-1):a=e+1}return s&&{element:s,top:s.getBoundingClientRect().top-r}}function x(){return typeof window.matchMedia==`function`&&window.matchMedia(`(prefers-reduced-motion: reduce)`).matches}function S(S){let{children:C,onLoadMore:w,ref:T,labels:E,className:D,style:O,classNames:k,styles:A,...j}=e(`ChatMessageList`,S),M=t(`ChatMessageList`,E,_),N=d(null),P=d(null),F=d(!0),I=d(0),L=d(null),R=d(null),[z,B]=f(!1),[V,H]=f(!1),{isLoading:U,sentinelRef:W}=o(N,w??y,!w),G=()=>{let e=N.current,t=P.current;if(!e||!t)return;let{scrollTop:n,scrollHeight:r,clientHeight:i}=e,a=r-n-i;n<I.current&&a>1?F.current=!1:a<=v&&(F.current=!0),I.current=n,B(!F.current),F.current&&H(!1),L.current=b(e,t,W.current)};u(()=>{let e=N.current,t=P.current;if(!e||!t)return;let n=()=>{let n=t.lastElementChild;if(!F.current&&n!==R.current&&H(!0),R.current=n,F.current)e.scrollTop=e.scrollHeight;else{let t=L.current;t?.element.isConnected&&(e.scrollTop+=t.element.getBoundingClientRect().top-e.getBoundingClientRect().top-t.top)}I.current=e.scrollTop,L.current=b(e,t,W.current)};n();let r=new ResizeObserver(n);return r.observe(e),r.observe(t),()=>r.disconnect()},[W]);let K=c(()=>{let e=N.current;e&&(F.current=!0,B(!1),H(!1),e.scrollTo({top:e.scrollHeight,behavior:x()?`auto`:`smooth`}))},[]);return l(T,()=>({scrollToBottom:K}),[K]),g(`div`,{...j,className:p(s.chatMessageList,D),style:O,children:[h(`div`,{ref:N,role:`log`,"aria-label":M.messages,"aria-busy":U||void 0,tabIndex:0,className:p(s.chatMessageListViewport,k?.viewport),style:A?.viewport,onScroll:G,children:g(`div`,{ref:P,className:p(s.chatMessageListContent,k?.content),style:A?.content,children:[w&&h(`div`,{ref:W,className:s.chatMessageListLoader,children:U&&h(r,{size:`1.5em`})}),C]})}),z&&h(a,{icon:m,styleType:V?i.Filled:i.Plain,color:V?n.Primary:n.Neutral,"aria-label":M.scrollToBottom,title:M.scrollToBottom,className:p(s.chatMessageListScrollButton,!V&&s.chatMessageListScrollButtonSurface,k?.scrollButton),style:A?.scrollButton,onClick:K})]})}export{S as default};
|
|
2
|
+
//# sourceMappingURL=chatMessageList.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chatMessageList.mjs","names":[],"sources":["../../../../package/common/chatMessageList/chatMessageList.tsx"],"sourcesContent":["import { useCallback, useImperativeHandle, useLayoutEffect, useRef, useState, type ReactElement } from 'react';\nimport cx from 'classnames';\nimport { faArrowDown } from '@fortawesome/free-solid-svg-icons';\n\nimport ActionIcon from '../actionIcon';\nimport { ButtonStyleType } from '../button/button.types';\nimport Loader from '../loader';\nimport { useComponentDefaults, useLabels } from '../configProvider';\nimport { useInfiniteScroll } from '../infiniteBlock/useInfiniteScroll';\nimport { ThemeColor } from '../themeProvider/color.types';\nimport type { ChatMessageListLabels, ChatMessageListProps } from './chatMessageList.types';\nimport styles from './chatMessageList.module.pcss';\n\nconst DEFAULT_LABELS: Required<ChatMessageListLabels> = {\n messages: 'Messages',\n scrollToBottom: 'Jump to latest',\n};\n\n/** How close to the bottom a user scrolling down gets before new messages are followed again. */\nconst STICK_DISTANCE = 32;\n\nconst NOTHING_TO_LOAD = () => Promise.resolve();\n\ninterface Anchor {\n element: Element;\n /** Distance from the viewport's top edge. */\n top: number;\n}\n\n/**\n * The first message still showing at the top of the viewport, found by bisection because a long\n * conversation has thousands of children and this runs on every scroll event.\n */\nfunction findAnchor(viewport: HTMLElement, content: HTMLElement, skip: Element | null): Anchor | null {\n const viewportTop = viewport.getBoundingClientRect().top;\n const items = content.children;\n let low = items[0] === skip ? 1 : 0;\n let high = items.length - 1;\n let found: Element | null = null;\n while (low <= high) {\n const middle = (low + high) >> 1;\n if (items[middle].getBoundingClientRect().bottom > viewportTop) {\n found = items[middle];\n high = middle - 1;\n } else {\n low = middle + 1;\n }\n }\n return found && { element: found, top: found.getBoundingClientRect().top - viewportTop };\n}\n\nfunction prefersReducedMotion(): boolean {\n return typeof window.matchMedia === 'function' && window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n}\n\nfunction ChatMessageList(props: ChatMessageListProps): ReactElement {\n const {\n children,\n onLoadMore,\n ref,\n labels,\n className,\n style,\n classNames,\n styles: slotStyles,\n ...rest\n } = useComponentDefaults('ChatMessageList', props);\n const text = useLabels('ChatMessageList', labels, DEFAULT_LABELS);\n const viewportRef = useRef<HTMLDivElement>(null);\n const contentRef = useRef<HTMLDivElement>(null);\n const isStuckRef = useRef(true);\n const lastScrollTopRef = useRef(0);\n const anchorRef = useRef<Anchor | null>(null);\n const lastChildRef = useRef<Element | null>(null);\n const [isAway, setIsAway] = useState(false);\n const [hasNew, setHasNew] = useState(false);\n const { isLoading, sentinelRef } = useInfiniteScroll(viewportRef, onLoadMore ?? NOTHING_TO_LOAD, !onLoadMore);\n\n const handleScroll = () => {\n const viewport = viewportRef.current;\n const content = contentRef.current;\n if (!viewport || !content) return;\n const { scrollTop, scrollHeight, clientHeight } = viewport;\n const distance = scrollHeight - scrollTop - clientHeight;\n // Any move up lets go, however small: a slow drag would otherwise be pulled back down by each\n // line of a streaming answer. The browser clamping scrollTop as content shrinks also moves it\n // up, but lands on the bottom, which is why the distance has to be checked too.\n if (scrollTop < lastScrollTopRef.current && distance > 1) isStuckRef.current = false;\n else if (distance <= STICK_DISTANCE) isStuckRef.current = true;\n lastScrollTopRef.current = scrollTop;\n setIsAway(!isStuckRef.current);\n if (isStuckRef.current) setHasNew(false);\n anchorRef.current = findAnchor(viewport, content, sentinelRef.current);\n };\n\n // Scroll anchoring by hand: Safari has no `overflow-anchor`, and where it exists it does nothing\n // at scrollTop 0, which is exactly where older messages get prepended. Runs in a ResizeObserver\n // callback, after layout and before paint, so neither the jump nor its correction is ever seen.\n useLayoutEffect(() => {\n const viewport = viewportRef.current;\n const content = contentRef.current;\n if (!viewport || !content) return undefined;\n\n const follow = () => {\n // A new last child is a message arriving; one prepended at the top or growing as it streams\n // leaves it the same.\n const last = content.lastElementChild;\n if (!isStuckRef.current && last !== lastChildRef.current) setHasNew(true);\n lastChildRef.current = last;\n if (isStuckRef.current) {\n viewport.scrollTop = viewport.scrollHeight;\n } else {\n const anchor = anchorRef.current;\n if (anchor?.element.isConnected) {\n viewport.scrollTop +=\n anchor.element.getBoundingClientRect().top - viewport.getBoundingClientRect().top - anchor.top;\n }\n }\n // So the scroll event this causes is not taken for the user scrolling up.\n lastScrollTopRef.current = viewport.scrollTop;\n anchorRef.current = findAnchor(viewport, content, sentinelRef.current);\n };\n\n follow();\n // The viewport too: a composer growing or a phone keyboard opening shrinks it from below.\n const observer = new ResizeObserver(follow);\n observer.observe(viewport);\n observer.observe(content);\n return () => observer.disconnect();\n }, [sentinelRef]);\n\n const scrollToBottom = useCallback(() => {\n const viewport = viewportRef.current;\n if (!viewport) return;\n isStuckRef.current = true;\n setIsAway(false);\n setHasNew(false);\n viewport.scrollTo({ top: viewport.scrollHeight, behavior: prefersReducedMotion() ? 'auto' : 'smooth' });\n }, []);\n\n useImperativeHandle(ref, () => ({ scrollToBottom }), [scrollToBottom]);\n\n return (\n <div {...rest} className={cx(styles.chatMessageList, className)} style={style}>\n <div\n ref={viewportRef}\n role='log'\n aria-label={text.messages}\n aria-busy={isLoading || undefined}\n // A stop of its own, so the history can be scrolled from the keyboard between messages\n // that hold nothing focusable.\n tabIndex={0}\n className={cx(styles.chatMessageListViewport, classNames?.viewport)}\n style={slotStyles?.viewport}\n onScroll={handleScroll}\n >\n <div\n ref={contentRef}\n className={cx(styles.chatMessageListContent, classNames?.content)}\n style={slotStyles?.content}\n >\n {onLoadMore && (\n <div ref={sentinelRef} className={styles.chatMessageListLoader}>\n {isLoading && <Loader size='1.5em' />}\n </div>\n )}\n {children}\n </div>\n </div>\n {isAway && (\n <ActionIcon\n icon={faArrowDown}\n styleType={hasNew ? ButtonStyleType.Filled : ButtonStyleType.Plain}\n color={hasNew ? ThemeColor.Primary : ThemeColor.Neutral}\n aria-label={text.scrollToBottom}\n title={text.scrollToBottom}\n className={cx(\n styles.chatMessageListScrollButton,\n !hasNew && styles.chatMessageListScrollButtonSurface,\n classNames?.scrollButton,\n )}\n style={slotStyles?.scrollButton}\n onClick={scrollToBottom}\n />\n )}\n </div>\n );\n}\n\nexport default ChatMessageList;\n"],"mappings":"qtBAaA,IAAM,EAAkD,CACtD,SAAU,WACV,eAAgB,gBAClB,EAGM,EAAiB,GAEjB,MAAwB,QAAQ,QAAQ,EAY9C,SAAS,EAAW,EAAuB,EAAsB,EAAqC,CACpG,IAAM,EAAc,EAAS,sBAAsB,CAAC,CAAC,IAC/C,EAAQ,EAAQ,SAClB,EAAM,IAAM,KAAO,GACnB,EAAO,EAAM,OAAS,EACtB,EAAwB,KAC5B,KAAO,GAAO,GAAM,CAClB,IAAM,EAAU,EAAM,GAAS,EAC3B,EAAM,EAAO,CAAC,sBAAsB,CAAC,CAAC,OAAS,GACjD,EAAQ,EAAM,GACd,EAAO,EAAS,GAEhB,EAAM,EAAS,CAEnB,CACA,OAAO,GAAS,CAAE,QAAS,EAAO,IAAK,EAAM,sBAAsB,CAAC,CAAC,IAAM,CAAY,CACzF,CAEA,SAAS,GAAgC,CACvC,OAAO,OAAO,OAAO,YAAe,YAAc,OAAO,WAAW,kCAAkC,CAAC,CAAC,OAC1G,CAEA,SAAS,EAAgB,EAA2C,CAClE,GAAM,CACJ,WACA,aACA,MACA,SACA,YACA,QACA,aACA,OAAQ,EACR,GAAG,GACD,EAAqB,kBAAmB,CAAK,EAC3C,EAAO,EAAU,kBAAmB,EAAQ,CAAc,EAC1D,EAAc,EAAuB,IAAI,EACzC,EAAa,EAAuB,IAAI,EACxC,EAAa,EAAO,EAAI,EACxB,EAAmB,EAAO,CAAC,EAC3B,EAAY,EAAsB,IAAI,EACtC,EAAe,EAAuB,IAAI,EAC1C,CAAC,EAAQ,GAAa,EAAS,EAAK,EACpC,CAAC,EAAQ,GAAa,EAAS,EAAK,EACpC,CAAE,YAAW,eAAgB,EAAkB,EAAa,GAAc,EAAiB,CAAC,CAAU,EAEtG,MAAqB,CACzB,IAAM,EAAW,EAAY,QACvB,EAAU,EAAW,QAC3B,GAAI,CAAC,GAAY,CAAC,EAAS,OAC3B,GAAM,CAAE,YAAW,eAAc,gBAAiB,EAC5C,EAAW,EAAe,EAAY,EAIxC,EAAY,EAAiB,SAAW,EAAW,EAAG,EAAW,QAAU,GACtE,GAAY,IAAgB,EAAW,QAAU,IAC1D,EAAiB,QAAU,EAC3B,EAAU,CAAC,EAAW,OAAO,EACzB,EAAW,SAAS,EAAU,EAAK,EACvC,EAAU,QAAU,EAAW,EAAU,EAAS,EAAY,OAAO,CACvE,EAKA,MAAsB,CACpB,IAAM,EAAW,EAAY,QACvB,EAAU,EAAW,QAC3B,GAAI,CAAC,GAAY,CAAC,EAAS,OAE3B,IAAM,MAAe,CAGnB,IAAM,EAAO,EAAQ,iBAGrB,GAFI,CAAC,EAAW,SAAW,IAAS,EAAa,SAAS,EAAU,EAAI,EACxE,EAAa,QAAU,EACnB,EAAW,QACb,EAAS,UAAY,EAAS,iBACzB,CACL,IAAM,EAAS,EAAU,QACrB,GAAQ,QAAQ,cAClB,EAAS,WACP,EAAO,QAAQ,sBAAsB,CAAC,CAAC,IAAM,EAAS,sBAAsB,CAAC,CAAC,IAAM,EAAO,IAEjG,CAEA,EAAiB,QAAU,EAAS,UACpC,EAAU,QAAU,EAAW,EAAU,EAAS,EAAY,OAAO,CACvE,EAEA,EAAO,EAEP,IAAM,EAAW,IAAI,eAAe,CAAM,EAG1C,OAFA,EAAS,QAAQ,CAAQ,EACzB,EAAS,QAAQ,CAAO,MACX,EAAS,WAAW,CACnC,EAAG,CAAC,CAAW,CAAC,EAEhB,IAAM,EAAiB,MAAkB,CACvC,IAAM,EAAW,EAAY,QACxB,IACL,EAAW,QAAU,GACrB,EAAU,EAAK,EACf,EAAU,EAAK,EACf,EAAS,SAAS,CAAE,IAAK,EAAS,aAAc,SAAU,EAAqB,EAAI,OAAS,QAAS,CAAC,EACxG,EAAG,CAAC,CAAC,EAIL,OAFA,EAAoB,OAAY,CAAE,gBAAe,GAAI,CAAC,CAAc,CAAC,EAGnE,EAAC,MAAD,CAAK,GAAI,EAAM,UAAW,EAAG,EAAO,gBAAiB,CAAS,EAAU,QAAxE,SAAA,CACE,EAAC,MAAD,CACE,IAAK,EACL,KAAK,MACL,aAAY,EAAK,SACjB,YAAW,GAAa,IAAA,GAGxB,SAAU,EACV,UAAW,EAAG,EAAO,wBAAyB,GAAY,QAAQ,EAClE,MAAO,GAAY,SACnB,SAAU,EAEV,SAAA,EAAC,MAAD,CACE,IAAK,EACL,UAAW,EAAG,EAAO,uBAAwB,GAAY,OAAO,EAChE,MAAO,GAAY,QAHrB,SAAA,CAKG,GACC,EAAC,MAAD,CAAK,IAAK,EAAa,UAAW,EAAO,sBACtC,SAAA,GAAa,EAAC,EAAD,CAAQ,KAAK,OAAS,CAAA,CACjC,CAAA,EAEN,CACE,GACF,CAAA,EACJ,GACC,EAAC,EAAD,CACE,KAAM,EACN,UAAW,EAAS,EAAgB,OAAS,EAAgB,MAC7D,MAAO,EAAS,EAAW,QAAU,EAAW,QAChD,aAAY,EAAK,eACjB,MAAO,EAAK,eACZ,UAAW,EACT,EAAO,4BACP,CAAC,GAAU,EAAO,mCAClB,GAAY,YACd,EACA,MAAO,GAAY,aACnB,QAAS,CACV,CAAA,CAEA,GAET"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";var e=`chatMessageList_u0P5F`,t=`chatMessageList-viewport_tgg1O`,n=`chatMessageList-content_ZAQDK`,r=`chatMessageList-loader_fWDBv`,i=`chatMessageList-scrollButton_jxrC8`,a=`chatMessageList-scrollButton-surface_28YFu`,o={chatMessageList:e,"chatMessageList-viewport":`chatMessageList-viewport_tgg1O`,chatMessageListViewport:t,"chatMessageList-content":`chatMessageList-content_ZAQDK`,chatMessageListContent:n,"chatMessageList-loader":`chatMessageList-loader_fWDBv`,chatMessageListLoader:r,"chatMessageList-scrollButton":`chatMessageList-scrollButton_jxrC8`,chatMessageListScrollButton:i,"chatMessageList-scrollButton-surface":`chatMessageList-scrollButton-surface_28YFu`,chatMessageListScrollButtonSurface:a};export{e as chatMessageList,n as chatMessageListContent,r as chatMessageListLoader,i as chatMessageListScrollButton,a as chatMessageListScrollButtonSurface,t as chatMessageListViewport,o as default};
|
|
2
|
+
//# sourceMappingURL=chatMessageList.module.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chatMessageList.module.mjs","names":[],"sources":["../../../../package/common/chatMessageList/chatMessageList.module.pcss"],"sourcesContent":[".chatMessageList {\n position: relative;\n display: flex;\n flex-direction: column;\n min-height: 0;\n\n &-viewport {\n display: flex;\n flex: 1 1 auto;\n flex-direction: column;\n min-height: 0;\n overflow-y: auto;\n\n /* The component anchors the scroll position itself; the browser's own would correct it twice. */\n overflow-anchor: none;\n overscroll-behavior: contain;\n\n &:focus-visible {\n outline: 2px solid var(--focus-border-color, var(--primary-color));\n outline-offset: -2px;\n }\n }\n\n /* An auto margin rather than justify-content: flex-end, which would put an overflow above the\n top where it can never be scrolled to. Keeps a short conversation at the bottom, by the composer. */\n &-content {\n display: flex;\n flex-direction: column;\n margin-top: auto;\n padding: 0.75em;\n }\n\n /* A fixed height, so the loader showing up inside it moves nothing. */\n &-loader {\n display: flex;\n flex: none;\n align-items: center;\n justify-content: center;\n height: 2.5em;\n }\n}\n\n/* Two classes: it is an ActionIcon, whose own one-class rules it would otherwise tie with. */\n.chatMessageList .chatMessageList-scrollButton {\n position: absolute;\n inset-inline-end: 0.75em;\n bottom: 0.75em;\n width: 2.5em;\n height: 2.5em;\n border-radius: 50%;\n box-shadow: var(--card-shadow);\n}\n\n/* A Plain button over the messages needs a surface of its own. Once something new has arrived\n below it is Filled, and keeps Button's fill. */\n.chatMessageList .chatMessageList-scrollButton-surface {\n background: var(--background-card-elevated, var(--background));\n}\n"],"mappings":""}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use client";import{useComponentDefaults as e}from"../configProvider/useComponentDefaults.mjs";import{usePresentationDefault as t}from"../configProvider/usePresentationDefault.mjs";import{Presentation as n}from"../configProvider/presentation.types.mjs";import r from"../loader/loader.mjs";import i from"../styles/sheet.module.mjs";import{PopoverAlign as ee,PopoverPlacement as te}from"../popover/popover.types.mjs";import ne from"../hooks/useSheet.mjs";import re from"../popover/popover.mjs";import ie from"../tooltip/tooltip.mjs";import{edgeIndex as
|
|
1
|
+
"use client";import{useComponentDefaults as e}from"../configProvider/useComponentDefaults.mjs";import{usePresentationDefault as t}from"../configProvider/usePresentationDefault.mjs";import{Presentation as n}from"../configProvider/presentation.types.mjs";import r from"../loader/loader.mjs";import i from"../styles/sheet.module.mjs";import{PopoverAlign as ee,PopoverPlacement as te}from"../popover/popover.types.mjs";import ne from"../hooks/useSheet.mjs";import re from"../popover/popover.mjs";import{useInfiniteScroll as ie}from"../infiniteBlock/useInfiniteScroll.mjs";import ae from"../tooltip/tooltip.mjs";import{edgeIndex as oe,nextIndex as se,scrollOptionIntoView as ce}from"../utils/listNavigation.mjs";import a from"./dropdown.module.mjs";import{useEffect as o,useId as le,useLayoutEffect as ue,useRef as s,useState as c}from"react";import l from"classnames";import{FontAwesomeIcon as de}from"@fortawesome/react-fontawesome";import{faChevronDown as fe}from"@fortawesome/free-solid-svg-icons";import{jsx as u,jsxs as d}from"react/jsx-runtime";var pe=500;function f(f){let{errorMessage:p=``,className:me=``,style:he,label:m=``,value:ge=``,isValid:_e=!0,tabIndex:ve=0,isRequired:ye=!1,customProps:h={},isRow:be=!1,disabled:g=!1,readOnly:_=!1,onScroll:xe=()=>Promise.resolve(),onClick:v,onChange:y,onFocus:Se=null,onBlur:Ce=null,onMouseEnter:we=null,onMouseLeave:Te=null,dropdownLabel:Ee,isDropdownListWider:b=!1,formValidator:x=null,classNames:S,styles:C,items:w=[],useMatLabelStyle:T=!0,alwaysFloatLabel:E=!1,placeholder:D=``,plainStyle:O=!1,scrollToSelected:De=!1,presentation:Oe,reactRef:ke,...Ae}=e(`Dropdown`,f),[je,Me]=c(!1),[Ne,Pe]=c(!1),[,Fe]=c(0),k=s(x),A=s(null),j=s(null),M=s(null),[Ie,Le]=c(!1),[N,P]=c(null),F=s({buffer:``,at:0}),I=le(),L=`${I}-listbox`,R=`${I}-label`,z=e=>`${I}-option-${e}`,B=!g&&!_,V=Ie&&B,{isLoading:Re,sentinelRef:ze}=ie(M,xe,!1),Be=t(),H=Oe??Be??n.Default,{isSheet:U}=ne(H);ue(()=>{let e=A.current,t=j.current;V&&!U&&e&&t&&(t.style.fontSize=window.getComputedStyle(e).fontSize)},[V,U]),o(()=>{if(k.current=x,!k.current)return;k.current.label=m??``,k.current.validate();let e=()=>Fe(e=>e+1);return k.current.registerOnUpdateListener(e),()=>k.current?.removeOnUpdateListener(e)},[x,m]);let W=x?x.value:ge,G=w.findIndex(e=>e.key===W);o(()=>{V&&N!==null&&ce(M.current,document.getElementById(z(N)))},[V,N]);let Ve=e=>{Pe(!0),we?.(e)},He=e=>{Pe(!1),Te?.(e)},Ue=e=>{Me(!0),Se?.(e)},We=e=>{k.current&&(k.current.touched=!0),Me(!1),Ce?.(e)},Ge=()=>x?x.touched&&x.hasError():!_e||!!p,K=()=>!!(ye||x?.validators.find(e=>e.validatorId===`required`)),Ke=e=>{x?(x.set(e),y?.(x.value,h)):y?.(e,h)};function q(e){(!e||B)&&(Le(e),e?P(De&&G>=0?G:null):F.current={buffer:``,at:0})}function J(e){e.disabled||(Ke(e.key),q(!1))}let qe={isDisabled:e=>!!w[e].disabled};function Je(e){let t=Date.now(),n=t-F.current.at>pe?e:F.current.buffer+e;F.current={buffer:n,at:t};let r=n.toLowerCase(),i=w.findIndex(e=>!e.disabled&&typeof e.label==`string`&&e.label.toLowerCase().startsWith(r));i!==-1&&(V?P(i):J(w[i]))}function Ye(e){if(B){if(!V){e.key===`ArrowDown`||e.key===`ArrowUp`||e.key===`Enter`||e.key===` `?(e.preventDefault(),q(!0)):e.key.length===1&&Je(e.key);return}if(e.key===`ArrowDown`||e.key===`ArrowUp`)e.preventDefault(),P(se(N,e.key===`ArrowDown`?1:-1,w.length,qe));else if(e.key===`Home`||e.key===`End`)e.preventDefault(),P(oe(e.key===`Home`?`first`:`last`,w.length,qe));else if(e.key===`Enter`||e.key===` `){e.preventDefault();let t=N===null?void 0:w[N];t?J(t):q(!1)}else e.key===`Escape`||e.key===`Tab`?q(!1):e.key.length===1&&Je(e.key)}}let Y=Ee,X=Ge();w.forEach(e=>{e.key===W&&(Y=e.label)});let Z=Y||W,Q=Z==null||typeof Z==`string`&&Z.trim()===``,Xe=K()?u(`span`,{className:a.requiredMark,children:`*`}):null,Ze=m&&K()?`${m} *`:m,$=je&&!_;return d(`div`,{...Ae,className:l(me,S?.root,a.dropdownLabel,a.dropdown,{[a.dropdownLabelColumn]:!be,[a.dropdownWithLabel]:T&&!!m}),style:{...he,...C?.root},onMouseEnter:Ve,onMouseLeave:He,children:[u(`div`,{children:u(re,{isOpen:V,onOpenChange:q,disabled:!B,placement:te.Bottom,align:ee.Start,offset:0,withArrow:!1,constrainWidth:!1,width:`trigger`,focusOnOpen:!1,presentation:H,classNames:{panel:a.dropdownPanel},content:u(`div`,{ref:j,className:l(!U&&a.dropdownInputListAnim,{[a.dropdownInputListAnimWider]:b&&!U}),children:d(`div`,{ref:M,id:L,role:`listbox`,"aria-label":m||D||void 0,className:l(a.dropdownInputList,U&&i.sheetScroll,S?.list),style:C?.list,children:[w.map((e,t)=>u(`div`,{id:z(t),role:`option`,"aria-selected":e.key===W,"aria-disabled":e.disabled||void 0,style:C?.option,className:l(a.dropdownInputListItem,S?.option,{[a.dropdownInputListItemActive]:N===t,[a.dropdownInputListItemSelected]:e.key===W,[a.dropdownInputListItemDisabled]:!!e.disabled}),onMouseDown:e=>e.preventDefault(),onMouseEnter:()=>P(t),onClick:()=>J(e),children:e.label},e.key)),u(`div`,{ref:ze,className:a.dropdownSentinel,"aria-hidden":`true`}),Re&&u(`div`,{className:a.dropdownLoadMore,"aria-hidden":`true`,children:u(r,{size:32})})]})}),children:d(`div`,{ref:A,onFocus:Ue,onBlur:We,className:l(a.dropdownInput,{[a.dropdownInputPlain]:O,[a.dropdownInputReadOnly]:_&&!g},S?.field),style:C?.field,children:[u(`button`,{tabIndex:ve,type:`button`,disabled:g,role:`combobox`,"aria-haspopup":`listbox`,"aria-expanded":V,"aria-controls":L,"aria-activedescendant":V&&N!==null?z(N):void 0,"aria-labelledby":m?R:void 0,"aria-label":m?void 0:D||void 0,"aria-readonly":_||void 0,className:l(a.dropdownInputButton,{[a.dropdownInputButtonDisabled]:g,[a.dropdownInputButtonReadOnly]:_&&!g},S?.control),style:{...b?{padding:`6px`,border:`none`}:{},...C?.control},onKeyDown:Ye,onClick:v,ref:ke,children:Q?`\xA0`:Z}),u(`button`,{type:`button`,tabIndex:-1,disabled:g,"aria-hidden":`true`,className:l(a.dropdownInputButtonIcon,S?.icon),style:C?.icon,onMouseDown:e=>e.preventDefault(),onClick:v,children:!g&&!_&&u(de,{icon:fe,className:l(a.dropdownInputButtonIconArrow,{[a.dropdownInputButtonIconArrowOpen]:V})})}),u(`fieldset`,{className:l(a.dropdownFieldset,{[a.dropdownBorderPlain]:O,[a.dropdownBorderFocused]:$,[a.dropdownBorderInvalid]:X,[a.dropdownBorderError]:X}),children:u(`legend`,{className:l(a.dropdownFieldsetLegend,{[a.dropdownFieldsetLegendActive]:T&&(E||$||!Q)}),children:u(`span`,{children:Ze})})}),T&&m&&d(`div`,{id:R,className:l(a.dropdownMatLabel,{[a.dropdownMatLabelFloating]:E||$||!Q},S?.label),style:C?.label,children:[m,Xe]}),T&&D&&u(`div`,{className:l(a.dropdownPlaceholder,$&&!Y&&a.dropdownPlaceholderShown),children:D})]})})}),m&&!T&&d(`div`,{id:R,className:l(a.dropdownLabelText,S?.label),style:C?.label,children:[m,Xe]}),u(ae,{variant:`error`,message:x?x.getCurrentErrorMessage():p||``,isVisible:X&&(je||Ne)})]})}export{f as default};
|
|
2
2
|
//# sourceMappingURL=dropdown.mjs.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";import{useLayoutEffect as e,useRef as t}from"react";function n(e){let t=e.parentElement,{minHeight:n}=t?.style??{},{alignSelf:r}=e.style;t&&(t.style.minHeight=`${t.offsetHeight}px`),e.style.alignSelf=`flex-start`,e.style.height=`auto`;let i=e.scrollHeight>e.clientHeight+1;return e.style.height=`${e.scrollHeight}px`,e.style.alignSelf=r,t&&(t.style.minHeight=n??``),i}function r(r,i,{enabled:a=!0,onGrow:o}={}){let s=t(o);e(()=>{s.current=o}),e(()=>{let e=r.current;if(!a||!e)return;let t=e.offsetWidth,i=0,o=new ResizeObserver(()=>{e.offsetWidth!==t&&(t=e.offsetWidth,cancelAnimationFrame(i),i=requestAnimationFrame(()=>{let t=n(e);s.current?.(t)}))});return o.observe(e),()=>{o.disconnect(),cancelAnimationFrame(i),e.style.height=``}},[r,a]),e(()=>{if(!a||!r.current)return;let e=n(r.current);s.current?.(e)},[r,i,a])}export{r as useAutoGrow};
|
|
2
|
+
//# sourceMappingURL=useAutoGrow.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAutoGrow.mjs","names":[],"sources":["../../../../package/common/hooks/useAutoGrow.ts"],"sourcesContent":["import { useLayoutEffect, useRef, type RefObject } from 'react';\n\ninterface AutoGrowOptions {\n enabled?: boolean;\n /** Whether the text needs more than the field's `rows`, after each change of `value` or width. */\n onGrow?: (isGrown: boolean) => void;\n}\n\n/** Sizes the field to its text, and says whether that took it past its `rows`. */\nfunction grow(field: HTMLElement): boolean {\n // Collapsing the field to measure it would shrink its parent for that moment. A scroller that\n // gains the room clamps its position and keeps it when the field grows back: a chat's message\n // list lost its bottom on every keystroke. Holding the parent's height keeps the collapse inside;\n // offsetHeight, as a transform (a modal scaling in) would make the rect too small to hold it.\n const parent = field.parentElement;\n const { minHeight } = parent?.style ?? {};\n const { alignSelf } = field.style;\n if (parent) parent.style.minHeight = `${parent.offsetHeight}px`;\n // A stretched field would grow back to the held height and never measure shorter (Input's is).\n field.style.alignSelf = 'flex-start';\n field.style.height = 'auto';\n // Collapsed, the field is as tall as its `rows`. The 1px absorbs rounding: a line is far more.\n const isGrown = field.scrollHeight > field.clientHeight + 1;\n field.style.height = `${field.scrollHeight}px`;\n field.style.alignSelf = alignSelf;\n if (parent) parent.style.minHeight = minHeight ?? '';\n return isGrown;\n}\n\n/**\n * Keeps a textarea as tall as its text, up to the `max-height` its stylesheet sets. It measures\n * again when `value` changes, so the field must be controlled, and when its width changes.\n */\nexport function useAutoGrow(\n ref: RefObject<HTMLElement | null>,\n value: unknown,\n { enabled = true, onGrow }: AutoGrowOptions = {},\n): void {\n // Kept in a ref so a fresh callback each render does not resubscribe the observer.\n const onGrowRef = useRef(onGrow);\n useLayoutEffect(() => {\n onGrowRef.current = onGrow;\n });\n\n useLayoutEffect(() => {\n const field = ref.current;\n if (!enabled || !field) return undefined;\n let width = field.offsetWidth;\n let frame = 0;\n // A new width rewraps the same text onto more or fewer lines. Growing in the callback itself\n // would resize what is observed, which browsers report as a ResizeObserver loop error, so it\n // waits a frame. The height it then changes is skipped, as the width is the same.\n const observer = new ResizeObserver(() => {\n if (field.offsetWidth === width) return;\n width = field.offsetWidth;\n cancelAnimationFrame(frame);\n frame = requestAnimationFrame(() => {\n const isGrown = grow(field);\n onGrowRef.current?.(isGrown);\n });\n });\n observer.observe(field);\n return () => {\n observer.disconnect();\n cancelAnimationFrame(frame);\n // Only clears a height this set, so an explicit one from a style prop survives while\n // disabled. Here rather than below, where it would collapse the field on every keystroke.\n field.style.height = '';\n };\n }, [ref, enabled]);\n\n useLayoutEffect(() => {\n if (!enabled || !ref.current) return;\n // Measured first: an optional call skips evaluating its argument when there is no callback.\n const isGrown = grow(ref.current);\n onGrowRef.current?.(isGrown);\n }, [ref, value, enabled]);\n}\n"],"mappings":"iEASA,SAAS,EAAK,EAA6B,CAKzC,IAAM,EAAS,EAAM,cACf,CAAE,aAAc,GAAQ,OAAS,CAAC,EAClC,CAAE,aAAc,EAAM,MACxB,IAAQ,EAAO,MAAM,UAAY,GAAG,EAAO,aAAa,KAE5D,EAAM,MAAM,UAAY,aACxB,EAAM,MAAM,OAAS,OAErB,IAAM,EAAU,EAAM,aAAe,EAAM,aAAe,EAI1D,MAHA,GAAM,MAAM,OAAS,GAAG,EAAM,aAAa,IAC3C,EAAM,MAAM,UAAY,EACpB,IAAQ,EAAO,MAAM,UAAY,GAAa,IAC3C,CACT,CAMA,SAAgB,EACd,EACA,EACA,CAAE,UAAU,GAAM,UAA4B,CAAC,EACzC,CAEN,IAAM,EAAY,EAAO,CAAM,EAC/B,MAAsB,CACpB,EAAU,QAAU,CACtB,CAAC,EAED,MAAsB,CACpB,IAAM,EAAQ,EAAI,QAClB,GAAI,CAAC,GAAW,CAAC,EAAO,OACxB,IAAI,EAAQ,EAAM,YACd,EAAQ,EAIN,EAAW,IAAI,mBAAqB,CACpC,EAAM,cAAgB,IAC1B,EAAQ,EAAM,YACd,qBAAqB,CAAK,EAC1B,EAAQ,0BAA4B,CAClC,IAAM,EAAU,EAAK,CAAK,EAC1B,EAAU,UAAU,CAAO,CAC7B,CAAC,EACH,CAAC,EAED,OADA,EAAS,QAAQ,CAAK,MACT,CACX,EAAS,WAAW,EACpB,qBAAqB,CAAK,EAG1B,EAAM,MAAM,OAAS,EACvB,CACF,EAAG,CAAC,EAAK,CAAO,CAAC,EAEjB,MAAsB,CACpB,GAAI,CAAC,GAAW,CAAC,EAAI,QAAS,OAE9B,IAAM,EAAU,EAAK,EAAI,OAAO,EAChC,EAAU,UAAU,CAAO,CAC7B,EAAG,CAAC,EAAK,EAAO,CAAO,CAAC,CAC1B"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use client";import{useEffect as e,useRef as t,useState as n}from"react";function
|
|
1
|
+
"use client";import{useEffect as e,useRef as t,useState as n}from"react";var r=3e3;function i(i,a,o){let[s,c]=n(!1),l=t(!1),u=t(null),d=t(a);return e(()=>{d.current=a}),e(()=>{let e=i.current,t=u.current;if(o||!e||!t)return;let n=!1,a=0,s=()=>{p.unobserve(t),p.observe(t)},f=()=>{if(l.current)return;l.current=!0,c(!0);let e=e=>{l.current=!1,c(!1),!n&&(e?s():a=window.setTimeout(s,r))};d.current().then(()=>e(!0),()=>e(!1))},p=new IntersectionObserver(([e])=>{e.isIntersecting&&f()},{root:e});return p.observe(t),()=>{n=!0,window.clearTimeout(a),p.disconnect()}},[i,o]),{isLoading:s,sentinelRef:u}}export{i as useInfiniteScroll};
|
|
2
2
|
//# sourceMappingURL=useInfiniteScroll.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useInfiniteScroll.mjs","names":[],"sources":["../../../../package/common/infiniteBlock/useInfiniteScroll.ts"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport type React from 'react';\n\n// Loads more items once a sentinel element at the end of the list scrolls into view.\n// IntersectionObserver only fires on a change of intersection state, so if a page of\n// freshly-loaded items still doesn't fill the container, the sentinel stays visible and\n// we have to re-arm the observer ourselves to pick up the next page.\nexport function useInfiniteScroll(\n containerRef: React.RefObject<HTMLDivElement | null>,\n onScroll: () => Promise<void>,\n disabled: boolean,\n) {\n const [isLoading, setIsLoading] = useState(false);\n const isLoadingRef = useRef(false);\n const sentinelRef = useRef<HTMLDivElement>(null);\n\n // Kept in a ref so a caller passing a fresh onScroll function each render doesn't\n // tear down and recreate the observer every time.\n const onScrollRef = useRef(onScroll);\n useEffect(() => {\n onScrollRef.current = onScroll;\n });\n\n useEffect(() => {\n const root = containerRef.current;\n const sentinel = sentinelRef.current;\n if (disabled || !root || !sentinel) return undefined;\n\n let cancelled = false;\n\n const loadMore = () => {\n if (isLoadingRef.current) return;\n isLoadingRef.current = true;\n setIsLoading(true);\n\n
|
|
1
|
+
{"version":3,"file":"useInfiniteScroll.mjs","names":[],"sources":["../../../../package/common/infiniteBlock/useInfiniteScroll.ts"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport type React from 'react';\n\nconst RETRY_DELAY = 3000;\n\n// Loads more items once a sentinel element at the end of the list scrolls into view.\n// IntersectionObserver only fires on a change of intersection state, so if a page of\n// freshly-loaded items still doesn't fill the container, the sentinel stays visible and\n// we have to re-arm the observer ourselves to pick up the next page.\nexport function useInfiniteScroll(\n containerRef: React.RefObject<HTMLDivElement | null>,\n onScroll: () => Promise<void>,\n disabled: boolean,\n) {\n const [isLoading, setIsLoading] = useState(false);\n const isLoadingRef = useRef(false);\n const sentinelRef = useRef<HTMLDivElement>(null);\n\n // Kept in a ref so a caller passing a fresh onScroll function each render doesn't\n // tear down and recreate the observer every time.\n const onScrollRef = useRef(onScroll);\n useEffect(() => {\n onScrollRef.current = onScroll;\n });\n\n useEffect(() => {\n const root = containerRef.current;\n const sentinel = sentinelRef.current;\n if (disabled || !root || !sentinel) return undefined;\n\n let cancelled = false;\n let retry = 0;\n\n // Observing again reports the sentinel at once, for a page that still left it in view.\n const rearm = () => {\n observer.unobserve(sentinel);\n observer.observe(sentinel);\n };\n\n const loadMore = () => {\n if (isLoadingRef.current) return;\n isLoadingRef.current = true;\n setIsLoading(true);\n\n const settle = (succeeded: boolean) => {\n // Reset even once cancelled: a load can outlive its observer when the caller disables it\n // mid-request, and a flag left set would block every load after it.\n isLoadingRef.current = false;\n setIsLoading(false);\n if (cancelled) return;\n if (succeeded) rearm();\n // A failure waits before trying again, or it would retry without end while the sentinel\n // stays in view. It does try again: a list too short to scroll offers no other way.\n else retry = window.setTimeout(rearm, RETRY_DELAY);\n };\n\n onScrollRef.current().then(\n () => settle(true),\n () => settle(false),\n );\n };\n\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting) loadMore();\n },\n { root },\n );\n\n observer.observe(sentinel);\n\n return () => {\n cancelled = true;\n window.clearTimeout(retry);\n observer.disconnect();\n };\n }, [containerRef, disabled]);\n\n return { isLoading, sentinelRef };\n}\n"],"mappings":"yEAGA,IAAM,EAAc,IAMpB,SAAgB,EACd,EACA,EACA,EACA,CACA,GAAM,CAAC,EAAW,GAAgB,EAAS,EAAK,EAC1C,EAAe,EAAO,EAAK,EAC3B,EAAc,EAAuB,IAAI,EAIzC,EAAc,EAAO,CAAQ,EA0DnC,OAzDA,MAAgB,CACd,EAAY,QAAU,CACxB,CAAC,EAED,MAAgB,CACd,IAAM,EAAO,EAAa,QACpB,EAAW,EAAY,QAC7B,GAAI,GAAY,CAAC,GAAQ,CAAC,EAAU,OAEpC,IAAI,EAAY,GACZ,EAAQ,EAGN,MAAc,CAClB,EAAS,UAAU,CAAQ,EAC3B,EAAS,QAAQ,CAAQ,CAC3B,EAEM,MAAiB,CACrB,GAAI,EAAa,QAAS,OAC1B,EAAa,QAAU,GACvB,EAAa,EAAI,EAEjB,IAAM,EAAU,GAAuB,CAGrC,EAAa,QAAU,GACvB,EAAa,EAAK,EACd,KACA,EAAW,EAAM,EAGhB,EAAQ,OAAO,WAAW,EAAO,CAAW,EACnD,EAEA,EAAY,QAAQ,CAAC,CAAC,SACd,EAAO,EAAI,MACX,EAAO,EAAK,CACpB,CACF,EAEM,EAAW,IAAI,sBAClB,CAAC,KAAW,CACP,EAAM,gBAAgB,EAAS,CACrC,EACA,CAAE,MAAK,CACT,EAIA,OAFA,EAAS,QAAQ,CAAQ,MAEZ,CACX,EAAY,GACZ,OAAO,aAAa,CAAK,EACzB,EAAS,WAAW,CACtB,CACF,EAAG,CAAC,EAAc,CAAQ,CAAC,EAEpB,CAAE,YAAW,aAAY,CAClC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use client";import{useComponentDefaults as e}from"../configProvider/useComponentDefaults.mjs";import{useLabels as t}from"../configProvider/useLabels.mjs";import{mergeRefs as n}from"../utils/mergeRefs.mjs";import ee from"../tooltip/tooltip.mjs";import
|
|
1
|
+
"use client";import{useComponentDefaults as e}from"../configProvider/useComponentDefaults.mjs";import{useLabels as t}from"../configProvider/useLabels.mjs";import{mergeRefs as n}from"../utils/mergeRefs.mjs";import{useAutoGrow as ee}from"../hooks/useAutoGrow.mjs";import te from"../tooltip/tooltip.mjs";import ne from"../animatedIcon/animatedIcon.mjs";import r from"./input.module.mjs";import{InputIconPosition as i,InputType as a}from"./input.types.mjs";import re,{useEffect as ie,useId as ae,useRef as oe,useState as o}from"react";import s from"classnames";import{FontAwesomeIcon as c}from"@fortawesome/react-fontawesome";import{faEnvelope as l,faEye as se,faEyeSlash as ce,faPen as u,faPhone as le,faSearch as ue,faXmark as de}from"@fortawesome/free-solid-svg-icons";import{jsx as d,jsxs as f}from"react/jsx-runtime";var fe={clear:`Clear`,showPassword:`Show password`,hidePassword:`Hide password`,clearSearch:`Clear search`},pe={[a.Phone]:le,[a.Email]:l,[a.Edit]:u};function p(e){if(e)return{role:`button`,tabIndex:0,onMouseDown:e=>e.preventDefault(),onKeyDown:t=>{(t.key===`Enter`||t.key===` `)&&(t.preventDefault(),e(t))}}}function m(l){let{name:u=``,id:le,label:m=``,errorMessage:me=``,isValid:he=!0,className:ge=``,style:_e,value:ve=``,icon:h=null,iconPosition:ye,onChange:g=()=>{},onFocus:_=null,onBlur:v=null,onMouseEnter:y=null,onMouseLeave:be=null,isUppercase:xe=!1,isRequired:Se=!1,tabIndex:Ce=0,type:b=void 0,isRow:we=!1,onIconClick:x=null,parse:S=null,formValidator:C=null,autoFocus:w=!1,suffix:T=null,upperRightLabel:E=null,labelInBorder:Te=!1,clearable:Ee=!1,onClear:De,min:Oe=null,max:ke=null,placeholder:D=``,useMatLabelStyle:O=!0,alwaysFloatLabel:Ae=!1,autoComplete:je=null,onClick:Me=()=>null,readOnly:k=!1,customInput:A,multiline:j=!1,rows:Ne,autoResize:M=!1,classNames:N,styles:P,disabled:F=!1,labels:Pe,ref:Fe,...I}=e(`Input`,l),L=t(`Input`,Pe,fe),[Ie,Le]=o(!1),[Re,ze]=o(!1),[Be,Ve]=o(!1),[R,He]=o(!1),[,Ue]=o(0),z=oe(null),B=oe(C),We=ae(),Ge=le||u||We,V=C?C.value:ve;ee(z,V,{enabled:M}),ie(()=>{w&&z.current&&setTimeout(()=>{z.current?.focus()},1)},[w]),ie(()=>{B.current=C;let e=B.current;if(!e)return;e.label=m??``,e.validate();let t=()=>Ue(e=>e+1);return e.registerOnUpdateListener(t),()=>{e.removeOnUpdateListener(t)}},[C,m]);let Ke=()=>C?C.touched&&C.hasError():!he||!!me,qe=()=>!!(Se||C&&C.validators.find(e=>e.validatorId===`required`)),Je=e=>{Ve(!0),y&&y(e)},Ye=e=>{Ve(!1),be&&be(e)},Xe=e=>{Le(!0),_&&_(e)},Ze=e=>{B.current&&(B.current.touched=!0),Le(!1),v&&v(e)},Qe=e=>{let t,n;typeof e==`string`?n=e:(t=e,n=e.target.value),S&&(n=S(n)),C&&C.set(n),g&&g(n,t)},$e=()=>{if(b!==a.Email)return b===a.Phone?`tel`:b===a.Search?`search`:b===`password`&&R?`text`:b},et=()=>{if(b===a.Email)return`email`;if(b===a.Phone)return`tel`;if(b===a.Search)return`search`},tt=b===a.Search||b===a.Password,nt=!!h,rt=nt&&!tt,it=b===a.Email||b===a.Phone||b===a.Search,H=(ye??(!O&&it?i.Left:i.Right))===i.Left,at={className:s(r.inputFieldIcon,{[r.inputFieldIconNotClickable]:!x,[r.inputFieldIconLeft]:H}),onClick:x??void 0,...p(x)},ot=b&&!nt?pe[b]:void 0,U=h,W=x??U?.props.onClick,st=rt&&U?re.cloneElement(U,{className:s(r.inputFieldIcon,{[r.inputFieldIconNotClickable]:!W,[r.inputFieldIconLeft]:H},U.props.className),onClick:W,...p(W)}):null,G=Ke(),K=m;K&&qe()&&(K=`${K} *`);let ct=qe()?d(`span`,{className:r.requiredMark,children:`*`}):null,q=Ie&&!k,J=O&&(Ae||q||V||Re||b===a.Date),lt=e=>{let{animationName:t}=e;if(t===r.inputAutofillStart){ze(!0);let t=e.currentTarget.value;t&&t!==String(V??``)&&Qe(t);return}if(t===r.inputAutofillEnd){ze(!1);return}I.onAnimationStart?.(e)},Y=!!V&&!F&&!k,X=b===a.Search&&Y;function ut(){He(!R)}let Z=st;b===a.Search?Z=d(ne,{icon:X?de:ue,className:s(r.inputFieldIcon,{[r.inputFieldIconNotClickable]:!X,[r.inputFieldIconLeft]:H}),onClick:X?$:void 0,...p(X?$:void 0),"aria-label":X?L.clearSearch:void 0}):b===a.Password?Z=d(c,{icon:R?ce:se,className:s(r.inputFieldIcon,{[r.inputFieldIconLeft]:H}),onClick:ut,...p(ut),"aria-label":R?L.hidePassword:L.showPassword}):ot&&(Z=d(c,{icon:ot,...at}));let dt=!!Z&&H,Q=A?.props;function $(){C&&C.set(``),g?.(``),De?.(),z.current?.focus()}return d(`div`,{className:s(r.input,{[r.inputIconLeft]:dt,[r.inputWithLabel]:O&&!!m},ge),style:_e,children:f(`label`,{htmlFor:Ge,className:s(r.inputLabel,N?.label,{[r.inputLabelColumn]:!we,[r.inputLabelInBorder]:Te}),style:P?.label,children:[m&&!O?f(`span`,{className:r.inputLabelText,children:[m,ct]}):null,E&&d(`div`,{className:s(r.inputUpperRightLabel,N?.upperRightLabel),style:P?.upperRightLabel,children:E}),f(`div`,{className:s(r.inputContainer,{[r.inputContainerMultiline]:j,[r.inputContainerResizable]:j&&!M,[r.inputContainerDisabled]:F},N?.container),style:P?.container,onMouseEnter:Je,onMouseLeave:Ye,children:[H&&Z,re.cloneElement(A||d(j?`textarea`:`input`,{}),{...I,className:s(r.inputField,{[r.inputFieldUppercase]:xe,[r.inputFieldDate]:b===a.Date,[r.inputFieldDefault]:b!==a.Date,[r.inputFieldWithIconLeft]:dt,[r.inputFieldMultiline]:j,[r.inputFieldMultilineAutoResize]:j&&M,[r.inputFieldReadOnly]:k&&!F},N?.input,Q?.className),style:Q?.style||P?.input?{...Q?.style,...P?.input}:void 0,name:u,id:Ge,value:V,tabIndex:Ce,ref:A?void 0:n(z,Fe),onChange:Qe,onAnimationStart:lt,onFocus:Xe,onBlur:Ze,placeholder:O?``:D,autoComplete:je,onClick:Me,readOnly:k,disabled:F,...!j&&{type:$e(),inputMode:I.inputMode??et(),min:Oe,max:ke},...j&&Ne!==void 0&&{rows:Ne}}),T&&d(`div`,{className:s(r.inputFieldSuffix,N?.suffix),style:P?.suffix,children:T}),Ee&&b!==a.Search&&d(c,{icon:de,className:s(r.inputFieldIcon,r.inputFieldIconClear,{[r.inputFieldIconClearHidden]:!Y}),onClick:Y?$:void 0,...p(Y?$:void 0),"aria-label":L.clear}),!H&&Z]}),d(`fieldset`,{className:s(r.inputFieldset,N?.fieldset,{[r.inputContainerBorderFocused]:q,[r.inputContainerBorderSearchFocused]:b===a.Search&&(q||!!V),[r.inputContainerBorderInvalid]:G,[r.inputContainerBorderError]:G}),style:P?.fieldset,children:d(`legend`,{className:s(r.inputFieldsetLegend,{[r.inputFieldsetLegendActive]:J}),children:d(`span`,{children:K})})}),O&&m&&f(`div`,{className:s(r.inputMatLabel,{[r.inputMatLabelFloating]:J,[r.inputMatLabelMultiline]:j&&!J}),children:[m,ct]}),O&&D&&d(`div`,{className:s(r.inputPlaceholder,q&&!V&&!Re&&r.inputPlaceholderShown,j&&r.inputPlaceholderMultiline),children:D}),d(te,{variant:`error`,message:C?C.getCurrentErrorMessage():me||``,isVisible:G&&(Ie||Be)})]})})}export{m as default};
|
|
2
2
|
//# sourceMappingURL=input.mjs.map
|