@ahrowe/ui 0.49.0 → 0.51.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/README.md +4 -1
- 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/dropdown/dropdown.types.d.ts +3 -2
- package/dist/cjs-types/common/hooks/useAutoGrow.d.ts +12 -0
- package/dist/cjs-types/common/infiniteBlock/useInfiniteScroll.d.ts +1 -1
- 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/dropdown/dropdown.mjs.map +1 -1
- package/dist/esm/common/dropdown/dropdown.module.mjs +1 -1
- package/dist/esm/common/dropdown/dropdown.module.mjs.map +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/loader/loader.module.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/dropdown/dropdown.types.d.ts +3 -2
- package/dist/types/common/hooks/useAutoGrow.d.ts +12 -0
- package/dist/types/common/infiniteBlock/useInfiniteScroll.d.ts +1 -1
- 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/Dropdown.md +17 -3
- package/docs/TabHeader.md +19 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# @ahrowe/ui
|
|
2
2
|
|
|
3
|
+
## 0.51.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- f26bb70: **Breaking:** `Dropdown`'s `onScroll`, which was never called, is `onLoadMore`, as on VirtualList and ChatMessageList. It loads as the end of the open list comes near, with a spinner meanwhile, and in a bottom sheet the list now scrolls itself. See "Migration from `onScroll`" in docs/Dropdown.md
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- f26bb70: `ChatMessageList` starts loading older messages shortly before the top comes into view, and no longer calls `onLoadMore` again after a load that added nothing
|
|
12
|
+
- f26bb70: Fixed `Loader`'s Spinner and Comet making a scroll container they end gain and lose a scrollbar on every turn, which pushed the loader sideways
|
|
13
|
+
|
|
14
|
+
## 0.50.0
|
|
15
|
+
|
|
16
|
+
### Minor Changes
|
|
17
|
+
|
|
18
|
+
- 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
|
|
19
|
+
- 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
|
|
20
|
+
- 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
|
|
21
|
+
- 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
|
|
22
|
+
- 9bb5c12: `TabHeaderTab`'s `title` and `subtitle` now take any `ReactNode`, not only a string
|
|
23
|
+
|
|
24
|
+
### Patch Changes
|
|
25
|
+
|
|
26
|
+
- d7ce064: Fixed `Textarea` with `autoResize` keeping its old height when a change of width rewrapped its text
|
|
27
|
+
|
|
3
28
|
## 0.49.0
|
|
4
29
|
|
|
5
30
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -39,7 +39,7 @@ Things that might rule the package out.
|
|
|
39
39
|
that and no polyfill ships. Two details want a newer Firefox: ScrollFade's edge fade eases from
|
|
40
40
|
128 and snaps before it, and OptionPicker rings the whole group from 121. Both still work either
|
|
41
41
|
way. CI only runs Chromium.
|
|
42
|
-
- **One stylesheet.** `@ahrowe/ui/style.css` is
|
|
42
|
+
- **One stylesheet.** `@ahrowe/ui/style.css` is 180 KB, 35 KB gzipped, and covers everything. There
|
|
43
43
|
is no per-component CSS. It sits in `@layer ahrowe-ui`, so your own unlayered CSS wins no matter
|
|
44
44
|
which loads first.
|
|
45
45
|
- **No right to left.** The stylesheets use `margin-left` and friends rather than logical
|
|
@@ -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 as the user nears 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;
|
|
@@ -8,7 +8,7 @@ export interface DropdownItem {
|
|
|
8
8
|
disabled?: boolean;
|
|
9
9
|
}
|
|
10
10
|
export type DropdownSlots = 'root' | 'label' | 'field' | 'control' | 'icon' | 'placeholder' | 'list' | 'option';
|
|
11
|
-
export interface DropdownProps extends Omit<HtmlProps, 'onFocus' | 'onBlur' | 'onMouseEnter' | 'onMouseLeave'> {
|
|
11
|
+
export interface DropdownProps extends Omit<HtmlProps, 'onFocus' | 'onBlur' | 'onMouseEnter' | 'onMouseLeave' | 'onScroll'> {
|
|
12
12
|
formValidator?: FormValidator | null;
|
|
13
13
|
onMouseEnter?: ((event: React.MouseEvent) => void) | null;
|
|
14
14
|
onMouseLeave?: ((event: React.MouseEvent) => void) | null;
|
|
@@ -27,7 +27,8 @@ export interface DropdownProps extends Omit<HtmlProps, 'onFocus' | 'onBlur' | 'o
|
|
|
27
27
|
isRow?: boolean;
|
|
28
28
|
disabled?: boolean;
|
|
29
29
|
readOnly?: boolean;
|
|
30
|
-
|
|
30
|
+
/** Loads more items as the end of the open list comes near. Pass `undefined` once there are no more. */
|
|
31
|
+
onLoadMore?: () => Promise<void>;
|
|
31
32
|
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
|
32
33
|
dropdownLabel?: string;
|
|
33
34
|
isDropdownListWider?: boolean;
|
|
@@ -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,5 +1,5 @@
|
|
|
1
1
|
import { default as React } from 'react';
|
|
2
|
-
export declare function useInfiniteScroll(containerRef: React.RefObject<HTMLDivElement | null>,
|
|
2
|
+
export declare function useInfiniteScroll(containerRef: React.RefObject<HTMLDivElement | null>, onLoadMore: (() => Promise<void>) | undefined): {
|
|
3
3
|
isLoading: boolean;
|
|
4
4
|
sentinelRef: React.RefObject<HTMLDivElement | null>;
|
|
5
5
|
};
|
|
@@ -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;function y(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 b(){return typeof window.matchMedia==`function`&&window.matchMedia(`(prefers-reduced-motion: reduce)`).matches}function x(x){let{children:S,onLoadMore:C,ref:w,labels:T,className:E,style:D,classNames:O,styles:k,...A}=e(`ChatMessageList`,x),j=t(`ChatMessageList`,T,_),M=d(null),N=d(null),P=d(!0),F=d(0),I=d(null),L=d(null),[R,z]=f(!1),[B,V]=f(!1),{isLoading:H,sentinelRef:U}=o(M,C),W=()=>{let e=M.current,t=N.current;if(!e||!t)return;let{scrollTop:n,scrollHeight:r,clientHeight:i}=e,a=r-n-i;n<F.current&&a>1?P.current=!1:a<=v&&(P.current=!0),F.current=n,z(!P.current),P.current&&V(!1),I.current=y(e,t,U.current)};u(()=>{let e=M.current,t=N.current;if(!e||!t)return;let n=()=>{let n=t.lastElementChild;if(!P.current&&n!==L.current&&V(!0),L.current=n,P.current)e.scrollTop=e.scrollHeight;else{let t=I.current;t?.element.isConnected&&(e.scrollTop+=t.element.getBoundingClientRect().top-e.getBoundingClientRect().top-t.top)}F.current=e.scrollTop,I.current=y(e,t,U.current)};n();let r=new ResizeObserver(n);return r.observe(e),r.observe(t),()=>r.disconnect()},[U]);let G=c(()=>{let e=M.current;e&&(P.current=!0,z(!1),V(!1),e.scrollTo({top:e.scrollHeight,behavior:b()?`auto`:`smooth`}))},[]);return l(w,()=>({scrollToBottom:G}),[G]),g(`div`,{...A,className:p(s.chatMessageList,E),style:D,children:[h(`div`,{ref:M,role:`log`,"aria-label":j.messages,"aria-busy":H||void 0,tabIndex:0,className:p(s.chatMessageListViewport,O?.viewport),style:k?.viewport,onScroll:W,children:g(`div`,{ref:N,className:p(s.chatMessageListContent,O?.content),style:k?.content,children:[C&&h(`div`,{ref:U,className:s.chatMessageListLoader,children:H&&h(r,{size:`1.5em`})}),S]})}),R&&h(a,{icon:m,styleType:B?i.Filled:i.Plain,color:B?n.Primary:n.Neutral,"aria-label":j.scrollToBottom,title:j.scrollToBottom,className:p(s.chatMessageListScrollButton,!B&&s.chatMessageListScrollButtonSurface,O?.scrollButton),style:k?.scrollButton,onClick:G})]})}export{x 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\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);\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,GAYvB,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,CAAU,EAEtE,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
|
|
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 ee from"../loader/loader.mjs";import r from"../styles/sheet.module.mjs";import{PopoverAlign as te,PopoverPlacement as ne}from"../popover/popover.types.mjs";import re from"../hooks/useSheet.mjs";import ie from"../popover/popover.mjs";import{useInfiniteScroll as ae}from"../infiniteBlock/useInfiniteScroll.mjs";import oe from"../tooltip/tooltip.mjs";import{edgeIndex as se,nextIndex as ce,scrollOptionIntoView as le}from"../utils/listNavigation.mjs";import i from"./dropdown.module.mjs";import{useEffect as ue,useId as de,useLayoutEffect as fe,useRef as a,useState as o}from"react";import s from"classnames";import{FontAwesomeIcon as pe}from"@fortawesome/react-fontawesome";import{faChevronDown as me}from"@fortawesome/free-solid-svg-icons";import{jsx as c,jsxs as l}from"react/jsx-runtime";var he=500;function u(u){let{errorMessage:ge=``,className:_e=``,style:ve,label:d=``,value:ye=``,isValid:be=!0,tabIndex:xe=0,isRequired:Se=!1,customProps:Ce={},isRow:we=!1,disabled:f=!1,readOnly:p=!1,onLoadMore:Te,onClick:m,onChange:h,onFocus:Ee=null,onBlur:De=null,onMouseEnter:Oe=null,onMouseLeave:ke=null,dropdownLabel:Ae,isDropdownListWider:je=!1,formValidator:g=null,classNames:_,styles:v,items:y=[],useMatLabelStyle:b=!0,alwaysFloatLabel:x=!1,placeholder:S=``,plainStyle:C=!1,scrollToSelected:Me=!1,presentation:Ne,reactRef:Pe,...Fe}=e(`Dropdown`,u),[w,T]=o(!1),[Ie,E]=o(!1),[,Le]=o(0),D=a(g),O=a(null),Re=a(null),k=a(null),[ze,Be]=o(!1),[A,j]=o(null),M=a({buffer:``,at:0}),N=de(),P=`${N}-listbox`,F=`${N}-label`,I=e=>`${N}-option-${e}`,L=!f&&!p,R=ze&&L,{isLoading:z,sentinelRef:Ve}=ae(k,R?Te:void 0),He=t(),B=Ne??He??n.Default,{isSheet:V}=re(B);fe(()=>{let e=O.current,t=Re.current;R&&!V&&e&&t&&(t.style.fontSize=window.getComputedStyle(e).fontSize)},[R,V]),ue(()=>{if(D.current=g,!D.current)return;D.current.label=d??``,D.current.validate();let e=()=>Le(e=>e+1);return D.current.registerOnUpdateListener(e),()=>D.current?.removeOnUpdateListener(e)},[g,d]);let H=g?g.value:ye,U=y.findIndex(e=>e.key===H);ue(()=>{R&&A!==null&&le(k.current,document.getElementById(I(A)))},[R,A]);let Ue=e=>{E(!0),Oe?.(e)},We=e=>{E(!1),ke?.(e)},Ge=e=>{T(!0),Ee?.(e)},Ke=e=>{D.current&&(D.current.touched=!0),T(!1),De?.(e)},qe=()=>g?g.touched&&g.hasError():!be||!!ge,W=()=>!!(Se||g?.validators.find(e=>e.validatorId===`required`)),Je=e=>{g?(g.set(e),h?.(g.value,Ce)):h?.(e,Ce)};function G(e){(!e||L)&&(Be(e),e?j(Me&&U>=0?U:null):M.current={buffer:``,at:0})}function K(e){e.disabled||(Je(e.key),G(!1))}let q={isDisabled:e=>!!y[e].disabled};function J(e){let t=Date.now(),n=t-M.current.at>he?e:M.current.buffer+e;M.current={buffer:n,at:t};let ee=n.toLowerCase(),r=y.findIndex(e=>!e.disabled&&typeof e.label==`string`&&e.label.toLowerCase().startsWith(ee));r!==-1&&(R?j(r):K(y[r]))}function Ye(e){if(L){if(!R){e.key===`ArrowDown`||e.key===`ArrowUp`||e.key===`Enter`||e.key===` `?(e.preventDefault(),G(!0)):e.key.length===1&&J(e.key);return}if(e.key===`ArrowDown`||e.key===`ArrowUp`)e.preventDefault(),j(ce(A,e.key===`ArrowDown`?1:-1,y.length,q));else if(e.key===`Home`||e.key===`End`)e.preventDefault(),j(se(e.key===`Home`?`first`:`last`,y.length,q));else if(e.key===`Enter`||e.key===` `){e.preventDefault();let t=A===null?void 0:y[A];t?K(t):G(!1)}else e.key===`Escape`||e.key===`Tab`?G(!1):e.key.length===1&&J(e.key)}}let Y=Ae,X=qe();y.forEach(e=>{e.key===H&&(Y=e.label)});let Z=Y||H,Q=Z==null||typeof Z==`string`&&Z.trim()===``,Xe=W()?c(`span`,{className:i.requiredMark,children:`*`}):null,Ze=d&&W()?`${d} *`:d,$=w&&!p;return l(`div`,{...Fe,className:s(_e,_?.root,i.dropdownLabel,i.dropdown,{[i.dropdownLabelColumn]:!we,[i.dropdownWithLabel]:b&&!!d}),style:{...ve,...v?.root},onMouseEnter:Ue,onMouseLeave:We,children:[c(`div`,{children:c(ie,{isOpen:R,onOpenChange:G,disabled:!L,placement:ne.Bottom,align:te.Start,offset:0,withArrow:!1,constrainWidth:!1,width:`trigger`,focusOnOpen:!1,presentation:B,classNames:{panel:i.dropdownPanel},content:c(`div`,{ref:Re,className:s(V?i.dropdownInputListSheet:i.dropdownInputListAnim,{[i.dropdownInputListAnimWider]:je&&!V}),children:l(`div`,{ref:k,id:P,role:`listbox`,"aria-label":d||S||void 0,"aria-busy":z||void 0,className:s(i.dropdownInputList,V&&r.sheetScroll,_?.list),style:v?.list,children:[y.map((e,t)=>c(`div`,{id:I(t),role:`option`,"aria-selected":e.key===H,"aria-disabled":e.disabled||void 0,style:v?.option,className:s(i.dropdownInputListItem,_?.option,{[i.dropdownInputListItemActive]:A===t,[i.dropdownInputListItemSelected]:e.key===H,[i.dropdownInputListItemDisabled]:!!e.disabled}),onMouseDown:e=>e.preventDefault(),onMouseEnter:()=>j(t),onClick:()=>K(e),children:e.label},e.key)),c(`div`,{ref:Ve,className:i.dropdownSentinel,"aria-hidden":`true`}),z&&c(`div`,{className:i.dropdownLoadMore,"aria-hidden":`true`,children:c(ee,{size:`1.125em`})})]})}),children:l(`div`,{ref:O,onFocus:Ge,onBlur:Ke,className:s(i.dropdownInput,{[i.dropdownInputPlain]:C,[i.dropdownInputReadOnly]:p&&!f},_?.field),style:v?.field,children:[c(`button`,{tabIndex:xe,type:`button`,disabled:f,role:`combobox`,"aria-haspopup":`listbox`,"aria-expanded":R,"aria-controls":P,"aria-activedescendant":R&&A!==null?I(A):void 0,"aria-labelledby":d?F:void 0,"aria-label":d?void 0:S||void 0,"aria-readonly":p||void 0,className:s(i.dropdownInputButton,{[i.dropdownInputButtonDisabled]:f,[i.dropdownInputButtonReadOnly]:p&&!f},_?.control),style:{...je?{padding:`6px`,border:`none`}:{},...v?.control},onKeyDown:Ye,onClick:m,ref:Pe,children:Q?`\xA0`:Z}),c(`button`,{type:`button`,tabIndex:-1,disabled:f,"aria-hidden":`true`,className:s(i.dropdownInputButtonIcon,_?.icon),style:v?.icon,onMouseDown:e=>e.preventDefault(),onClick:m,children:!f&&!p&&c(pe,{icon:me,className:s(i.dropdownInputButtonIconArrow,{[i.dropdownInputButtonIconArrowOpen]:R})})}),c(`fieldset`,{className:s(i.dropdownFieldset,{[i.dropdownBorderPlain]:C,[i.dropdownBorderFocused]:$,[i.dropdownBorderInvalid]:X,[i.dropdownBorderError]:X}),children:c(`legend`,{className:s(i.dropdownFieldsetLegend,{[i.dropdownFieldsetLegendActive]:b&&(x||$||!Q)}),children:c(`span`,{children:Ze})})}),b&&d&&l(`div`,{id:F,className:s(i.dropdownMatLabel,{[i.dropdownMatLabelFloating]:x||$||!Q},_?.label),style:v?.label,children:[d,Xe]}),b&&S&&c(`div`,{className:s(i.dropdownPlaceholder,$&&!Y&&i.dropdownPlaceholderShown),children:S})]})})}),d&&!b&&l(`div`,{id:F,className:s(i.dropdownLabelText,_?.label),style:v?.label,children:[d,Xe]}),c(oe,{variant:`error`,message:g?g.getCurrentErrorMessage():ge||``,isVisible:X&&(w||Ie)})]})}export{u as default};
|
|
2
2
|
//# sourceMappingURL=dropdown.mjs.map
|