@cratis/components 4.19.2 → 4.20.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/README.md +2 -2
- package/compat-manifest.json +11 -11
- package/dist/esm/Chat/ChatSidebar.d.ts +4 -1
- package/dist/esm/Chat/ChatSidebar.d.ts.map +1 -1
- package/dist/esm/Chat/ChatSidebar.js +2 -2
- package/dist/esm/Chat/ChatSidebar.js.map +1 -1
- package/dist/esm/Chat/ChatTopicAction.d.ts +23 -0
- package/dist/esm/Chat/ChatTopicAction.d.ts.map +1 -0
- package/dist/esm/Chat/ChatTopicAction.js +4 -0
- package/dist/esm/Chat/ChatTopicAction.js.map +1 -0
- package/dist/esm/Chat/ChatTopicActions.d.ts +25 -0
- package/dist/esm/Chat/ChatTopicActions.d.ts.map +1 -0
- package/dist/esm/Chat/ChatTopicActions.js +6 -0
- package/dist/esm/Chat/ChatTopicActions.js.map +1 -0
- package/dist/esm/Chat/ChatTopicList.d.ts +5 -2
- package/dist/esm/Chat/ChatTopicList.d.ts.map +1 -1
- package/dist/esm/Chat/ChatTopicList.js +17 -9
- package/dist/esm/Chat/ChatTopicList.js.map +1 -1
- package/dist/esm/Chat/index.d.ts +1 -0
- package/dist/esm/Chat/index.d.ts.map +1 -1
- package/dist/esm/Chat/useTopicActionFocusRecovery.d.ts +19 -0
- package/dist/esm/Chat/useTopicActionFocusRecovery.d.ts.map +1 -0
- package/dist/esm/Chat/useTopicActionFocusRecovery.js +41 -0
- package/dist/esm/Chat/useTopicActionFocusRecovery.js.map +1 -0
- package/dist/esm/styles.Canvas.css +17 -1
- package/dist/esm/styles.Chat.css +17 -1
- package/dist/esm/styles.css +17 -1
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ The current package manifest declares these peer dependencies:
|
|
|
27
27
|
- optional `pixi.js` `^8.20.0`
|
|
28
28
|
- `react` and `react-dom` `^19.0.0`
|
|
29
29
|
- `reflect-metadata` `0.2.2`
|
|
30
|
-
- `tsyringe` `4.10.0`
|
|
30
|
+
- optional `tsyringe` `4.10.0` (Arc React installs it as its own dependency)
|
|
31
31
|
|
|
32
32
|
Strict installers can declare them explicitly; keep both Arc packages on the version your generated proxies were produced with. This repository builds and tests against Arc 22.16.0:
|
|
33
33
|
|
|
@@ -36,7 +36,7 @@ ARC_VERSION=22.16.0
|
|
|
36
36
|
npm install @cratis/components@^4 \
|
|
37
37
|
"@cratis/arc@$ARC_VERSION" "@cratis/arc.react@$ARC_VERSION" \
|
|
38
38
|
@cratis/fundamentals@^7.19.2 react@^19 react-dom@^19 \
|
|
39
|
-
reflect-metadata@0.2.2
|
|
39
|
+
reflect-metadata@0.2.2
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
The current manifest does not declare PrimeReact, PrimeIcons, or PrimeUI packages as dependencies or peers. Applications retaining direct dependencies keep their own package, provider, styling, and license boundaries.
|
package/compat-manifest.json
CHANGED
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
{
|
|
69
69
|
"name": "@cratis/components",
|
|
70
70
|
"role": "core",
|
|
71
|
-
"version": "4.
|
|
71
|
+
"version": "4.20.0",
|
|
72
72
|
"releaseMajorRange": ">=4 <5",
|
|
73
73
|
"independentRelease": false,
|
|
74
74
|
"private": false,
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
{
|
|
93
93
|
"name": "@cratis/eslint-plugin-components",
|
|
94
94
|
"role": "eslint",
|
|
95
|
-
"version": "4.
|
|
95
|
+
"version": "4.20.0",
|
|
96
96
|
"releaseMajorRange": ">=4 <5",
|
|
97
97
|
"independentRelease": false,
|
|
98
98
|
"private": false,
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
{
|
|
106
106
|
"name": "@cratis/components.migrator",
|
|
107
107
|
"role": "migrator",
|
|
108
|
-
"version": "4.
|
|
108
|
+
"version": "4.20.0",
|
|
109
109
|
"releaseMajorRange": ">=4 <5",
|
|
110
110
|
"independentRelease": false,
|
|
111
111
|
"private": false,
|
|
@@ -116,13 +116,13 @@
|
|
|
116
116
|
{
|
|
117
117
|
"name": "@cratis/components.conformance",
|
|
118
118
|
"role": "conformance",
|
|
119
|
-
"version": "4.
|
|
119
|
+
"version": "4.20.0",
|
|
120
120
|
"releaseMajorRange": ">=4 <5",
|
|
121
121
|
"independentRelease": false,
|
|
122
122
|
"private": false,
|
|
123
123
|
"packageAccess": "public",
|
|
124
124
|
"peerDependencies": {
|
|
125
|
-
"@cratis/components": "4.
|
|
125
|
+
"@cratis/components": "4.20.0",
|
|
126
126
|
"react": "^19.0.0",
|
|
127
127
|
"react-dom": "^19.0.0"
|
|
128
128
|
},
|
|
@@ -132,13 +132,13 @@
|
|
|
132
132
|
{
|
|
133
133
|
"name": "@cratis/components.mui",
|
|
134
134
|
"role": "renderer-adapter",
|
|
135
|
-
"version": "4.
|
|
135
|
+
"version": "4.20.0",
|
|
136
136
|
"releaseMajorRange": ">=4 <5",
|
|
137
137
|
"independentRelease": false,
|
|
138
138
|
"private": false,
|
|
139
139
|
"packageAccess": "public",
|
|
140
140
|
"peerDependencies": {
|
|
141
|
-
"@cratis/components": "4.
|
|
141
|
+
"@cratis/components": "4.20.0",
|
|
142
142
|
"@emotion/react": ">=11.5 <12",
|
|
143
143
|
"@emotion/styled": ">=11.11 <12",
|
|
144
144
|
"@mui/material": ">=9 <10",
|
|
@@ -188,13 +188,13 @@
|
|
|
188
188
|
{
|
|
189
189
|
"name": "@cratis/components.primereact",
|
|
190
190
|
"role": "renderer-adapter",
|
|
191
|
-
"version": "4.
|
|
191
|
+
"version": "4.20.0",
|
|
192
192
|
"releaseMajorRange": ">=4 <5",
|
|
193
193
|
"independentRelease": false,
|
|
194
194
|
"private": false,
|
|
195
195
|
"packageAccess": "public",
|
|
196
196
|
"peerDependencies": {
|
|
197
|
-
"@cratis/components": "4.
|
|
197
|
+
"@cratis/components": "4.20.0",
|
|
198
198
|
"@primereact/core": ">=11 <12",
|
|
199
199
|
"@primereact/ui": ">=11 <12",
|
|
200
200
|
"@primeuix/themes": ">=3 <4",
|
|
@@ -256,13 +256,13 @@
|
|
|
256
256
|
{
|
|
257
257
|
"name": "@cratis/components.primereact10",
|
|
258
258
|
"role": "renderer-adapter",
|
|
259
|
-
"version": "4.
|
|
259
|
+
"version": "4.20.0",
|
|
260
260
|
"releaseMajorRange": ">=4 <5",
|
|
261
261
|
"independentRelease": false,
|
|
262
262
|
"private": false,
|
|
263
263
|
"packageAccess": "public",
|
|
264
264
|
"peerDependencies": {
|
|
265
|
-
"@cratis/components": "4.
|
|
265
|
+
"@cratis/components": "4.20.0",
|
|
266
266
|
"primereact": ">=10.9.9 <11",
|
|
267
267
|
"react": "^19.0.0",
|
|
268
268
|
"react-dom": "^19.0.0"
|
|
@@ -4,6 +4,7 @@ import { type ChatIdentifier } from './ChatIdentifier.js';
|
|
|
4
4
|
import type { ChatMention } from './ChatMention.js';
|
|
5
5
|
import type { ChatMessage } from './ChatMessage.js';
|
|
6
6
|
import type { ChatTopic } from './ChatTopic.js';
|
|
7
|
+
import type { ChatTopicAction } from './ChatTopicAction.js';
|
|
7
8
|
import type { ChatStatus } from './ChatStatus.js';
|
|
8
9
|
import type { ChatTopicListLabels } from './ChatTopicList.js';
|
|
9
10
|
type ChatSidebarPartAttributes<TElement> = HTMLAttributes<TElement> & {
|
|
@@ -64,6 +65,8 @@ export interface ChatSidebarProps<TMessage extends ChatMessage = ChatMessage, TT
|
|
|
64
65
|
* (observable) query delivers and the conversation re-renders as it changes.
|
|
65
66
|
*/
|
|
66
67
|
messages: TMessage[];
|
|
68
|
+
/** The host's actions to offer on each topic where they are available. The inherited `actions` prop applies only to messages. */
|
|
69
|
+
topicActions?: ChatTopicAction<TTopic>[];
|
|
67
70
|
/** Topic-list query display state. Defaults to ready when omitted. */
|
|
68
71
|
topicsStatus?: ChatStatus;
|
|
69
72
|
/** Conversation query display state. Defaults to ready when omitted. */
|
|
@@ -138,6 +141,6 @@ export interface ChatSidebarProps<TMessage extends ChatMessage = ChatMessage, TT
|
|
|
138
141
|
* a name through {@link ChatSidebarProps.onRequestTopicName | onRequestTopicName} — rendering a
|
|
139
142
|
* pending placeholder until the name arrives.
|
|
140
143
|
*/
|
|
141
|
-
export declare const ChatSidebar: <TMessage extends ChatMessage = ChatMessage, TTopic extends ChatTopic = ChatTopic>({ open, onClose, topics, messages, topicsStatus, messagesStatus, selectedTopicId, onTopicSelected, onStartTopic, onSendMessage, onRequestTopicName, isTopicUnnamed, position, width, modal, labels, className, pt, ...conversation }: ChatSidebarProps<TMessage, TTopic>) => false | import("react").JSX.Element | null;
|
|
144
|
+
export declare const ChatSidebar: <TMessage extends ChatMessage = ChatMessage, TTopic extends ChatTopic = ChatTopic>({ open, onClose, topics, messages, topicActions, topicsStatus, messagesStatus, selectedTopicId, onTopicSelected, onStartTopic, onSendMessage, onRequestTopicName, isTopicUnnamed, position, width, modal, labels, className, pt, ...conversation }: ChatSidebarProps<TMessage, TTopic>) => false | import("react").JSX.Element | null;
|
|
142
145
|
export {};
|
|
143
146
|
//# sourceMappingURL=ChatSidebar.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatSidebar.d.ts","sourceRoot":"","sources":["../../../Chat/ChatSidebar.tsx"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"ChatSidebar.d.ts","sourceRoot":"","sources":["../../../Chat/ChatSidebar.tsx"],"names":[],"mappings":"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"}
|
|
@@ -12,7 +12,7 @@ import { unstable_useOverlayEnvironment } from '../renderer/RendererContext.js';
|
|
|
12
12
|
|
|
13
13
|
const classNames = (...values) => values.filter(Boolean).join(' ');
|
|
14
14
|
const BackIcon = () => (jsxs("svg", { width: '14', height: '14', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: '2.5', "aria-hidden": 'true', children: [jsx("line", { x1: '19', y1: '12', x2: '5', y2: '12' }), jsx("polyline", { points: '12 19 5 12 12 5' })] }));
|
|
15
|
-
const ChatSidebar = ({ open, onClose, topics, messages, topicsStatus, messagesStatus, selectedTopicId, onTopicSelected, onStartTopic, onSendMessage, onRequestTopicName, isTopicUnnamed: isTopicUnnamed$1, position = 'right', width = '24rem', modal = false, labels, className, pt, ...conversation }) => {
|
|
15
|
+
const ChatSidebar = ({ open, onClose, topics, messages, topicActions, topicsStatus, messagesStatus, selectedTopicId, onTopicSelected, onStartTopic, onSendMessage, onRequestTopicName, isTopicUnnamed: isTopicUnnamed$1, position = 'right', width = '24rem', modal = false, labels, className, pt, ...conversation }) => {
|
|
16
16
|
const icon = useCratisIcon();
|
|
17
17
|
const overlayEnvironment = unstable_useOverlayEnvironment();
|
|
18
18
|
const [mounted, setMounted] = useState(false);
|
|
@@ -82,7 +82,7 @@ const ChatSidebar = ({ open, onClose, topics, messages, topicsStatus, messagesSt
|
|
|
82
82
|
const titleIsPending = openTopicId !== undefined && (!openTopic || unnamed(openTopic));
|
|
83
83
|
const panel = (jsxs(Fragment, { children: [jsxs("header", { ...pt?.header, className: classNames('cratis-chat-sidebar__header', pt?.header?.className), "data-cratis-part": 'header', children: [openTopicId !== undefined && (jsx("button", { ...pt?.back, type: 'button', className: classNames('cratis-chat-sidebar__back', pt?.back?.className), "data-cratis-part": 'back', title: labels?.back ?? 'Back to topics', "aria-label": labels?.back ?? 'Back to topics', onClick: () => select(undefined), children: jsx(BackIcon, {}) })), jsx("h2", { ...pt?.title, className: classNames('cratis-chat-sidebar__title', titleIsPending
|
|
84
84
|
? 'cratis-chat-sidebar__title--pending'
|
|
85
|
-
: undefined, pt?.title?.className), "data-cratis-part": 'title', children: title }), jsx("button", { ...pt?.close, type: 'button', className: classNames('cratis-chat-sidebar__close', pt?.close?.className), "data-cratis-part": 'close', "aria-label": labels?.close ?? 'Close chat', onClick: onClose, children: jsx("span", { "aria-hidden": 'true', children: icon('close', '×') }) })] }), jsx("div", { ...pt?.content, className: classNames('cratis-chat-sidebar__content', pt?.content?.className), "data-cratis-part": 'content', children: openTopicId === undefined ? (jsx(ChatTopicList, { topics: topics, status: topicsStatus, onOpen: (topic) => select(topic.id, topic), onStart: onStartTopic
|
|
85
|
+
: undefined, pt?.title?.className), "data-cratis-part": 'title', children: title }), jsx("button", { ...pt?.close, type: 'button', className: classNames('cratis-chat-sidebar__close', pt?.close?.className), "data-cratis-part": 'close', "aria-label": labels?.close ?? 'Close chat', onClick: onClose, children: jsx("span", { "aria-hidden": 'true', children: icon('close', '×') }) })] }), jsx("div", { ...pt?.content, className: classNames('cratis-chat-sidebar__content', pt?.content?.className), "data-cratis-part": 'content', children: openTopicId === undefined ? (jsx(ChatTopicList, { topics: topics, topicActions: topicActions, status: topicsStatus, onOpen: (topic) => select(topic.id, topic), onStart: onStartTopic
|
|
86
86
|
? () => {
|
|
87
87
|
void startTopic();
|
|
88
88
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatSidebar.js","sources":["../../../Chat/ChatSidebar.tsx"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { useEffect, useRef, useState } from 'react';\nimport type { ButtonHTMLAttributes, HTMLAttributes } from 'react';\nimport { createPortal } from 'react-dom';\nimport { Modal, ModalOverlay } from 'react-aria-components';\nimport type { ChatConversationLabels, ChatConversationProps } from './ChatConversation';\nimport { ChatConversation } from './ChatConversation';\nimport { sameChatIdentifier, type ChatIdentifier } from './ChatIdentifier';\nimport type { ChatMention } from './ChatMention';\nimport type { ChatMessage } from './ChatMessage';\nimport type { ChatTopic } from './ChatTopic';\nimport type { ChatStatus } from './ChatStatus';\nimport type { ChatTopicListLabels } from './ChatTopicList';\nimport { ChatTopicList } from './ChatTopicList';\nimport { isTopicUnnamed as defaultIsTopicUnnamed } from './isTopicUnnamed';\nimport { shouldRequestTopicName } from './shouldRequestTopicName';\nimport { useCratisIcon } from '../configuration/useCratisIcon';\nimport { unstable_useOverlayEnvironment } from '../renderer/RendererContext';\nimport type { ExactPartKeys } from '../types/ExactPartKeys';\nimport type { PartsOf } from '../types/parts';\n\ntype ChatSidebarPartAttributes<TElement> = HTMLAttributes<TElement> & {\n [attribute: `data-${string}`]: string | number | boolean | undefined;\n};\n\ntype ChatSidebarButtonAttributes = Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n 'onClick' | 'type'\n>;\n\n/** Stable Cratis-owned parts for styling a {@link ChatSidebar}. */\nexport interface ChatSidebarParts {\n /** Viewport overlay layer the sidebar portals into — only paints/blocks when {@link ChatSidebarProps.modal} is true. */\n backdrop?: ChatSidebarPartAttributes<HTMLDivElement>;\n /** The sidebar panel itself. */\n root?: ChatSidebarPartAttributes<HTMLDivElement>;\n /** Header containing the back affordance, title, and close button. */\n header?: ChatSidebarPartAttributes<HTMLElement>;\n /** The topic list title, or the open topic's name. */\n title?: ChatSidebarPartAttributes<HTMLHeadingElement>;\n /** Back-to-topics button, present only while a topic is open. */\n back?: ChatSidebarButtonAttributes;\n /** Close button. */\n close?: ChatSidebarButtonAttributes;\n /** The topic list / conversation content region. */\n content?: ChatSidebarPartAttributes<HTMLDivElement>;\n}\n\nconst chatSidebarPartsMatchManifest: ExactPartKeys<\n ChatSidebarParts,\n PartsOf<'ChatSidebar'>\n> = true;\nvoid chatSidebarPartsMatchManifest;\n\n/** Overrides for every label the {@link ChatSidebar} and its children render. Any field left\n * unset falls back to a literal English default. */\nexport interface ChatSidebarLabels {\n /** The sidebar's title while the topic list is showing. Defaults to `'Topics'`. */\n topics?: string;\n\n /** Placeholder title for a topic that has not been named yet. Defaults to `'New topic'`. */\n unnamedTopic?: string;\n\n /** Tooltip/accessible label for the back-to-topics button. Defaults to `'Back to topics'`. */\n back?: string;\n\n /** Accessible label for the close button. Defaults to `'Close chat'`. */\n close?: string;\n\n /** Labels forwarded to the {@link ChatTopicList}. */\n topicList?: ChatTopicListLabels;\n\n /** Labels forwarded to the {@link ChatConversation}. */\n conversation?: ChatConversationLabels;\n}\n\n/**\n * Props for {@link ChatSidebar}.\n * @typeParam TMessage The host's message type — anything extending {@link ChatMessage}.\n * @typeParam TTopic The host's topic type — anything extending {@link ChatTopic}.\n */\nexport interface ChatSidebarProps<\n TMessage extends ChatMessage = ChatMessage,\n TTopic extends ChatTopic = ChatTopic,\n> extends Omit<\n ChatConversationProps<TMessage>,\n 'messages' | 'onSendMessage' | 'labels' | 'className' | 'status'\n> {\n /** Whether the sidebar is open. */\n open: boolean;\n\n /** Invoked when the sidebar asks to close — the host owns {@link open}, so closing means the host flips it. */\n onClose: () => void;\n\n /**\n * The topics, in any order. Hand this the array a live (observable) query delivers and the\n * sidebar re-renders as it changes.\n */\n topics: TTopic[];\n\n /**\n * The messages — either the whole chat or just the open topic's; the sidebar shows those of\n * the open topic by matching on `topicId` either way. Hand this the array a live\n * (observable) query delivers and the conversation re-renders as it changes.\n */\n messages: TMessage[];\n\n /** Topic-list query display state. Defaults to ready when omitted. */\n topicsStatus?: ChatStatus;\n\n /** Conversation query display state. Defaults to ready when omitted. */\n messagesStatus?: ChatStatus;\n\n /**\n * The open topic, for hosts that own the selection themselves: an identifier opens that\n * topic's conversation, `null` shows the topic list, and leaving the prop unset lets the\n * sidebar keep the selection internally.\n */\n selectedTopicId?: ChatIdentifier | null;\n\n /**\n * Invoked when the open topic changes — a topic was opened (or just started), or the person\n * went back to the list.\n * @param topicId The identifier of the topic now open, or undefined for the list.\n * @param topic The topic itself, when it is known in {@link topics}.\n */\n onTopicSelected?: (topicId: ChatIdentifier | undefined, topic?: TTopic) => void;\n\n /**\n * Invoked when a new topic is asked for. Creating the topic is the host's business; answer\n * with its identifier (directly or through a promise) and the sidebar opens it, ready for\n * the first message. Omit to leave the new-topic affordance off.\n * @returns The identifier of the topic that was started, when the host created one.\n */\n onStartTopic?: () => ChatIdentifier | undefined | Promise<ChatIdentifier | undefined>;\n\n /**\n * Invoked when a message is sent, with the topic it belongs to, the trimmed body and who it\n * mentions. Everything else about persisting the message is the host's business.\n * @param topicId The identifier of the topic the message was sent in.\n * @param body The message text, mentions written into it as plain `@Name`.\n * @param mentions Who the body mentions.\n */\n onSendMessage: (\n topicId: ChatIdentifier,\n body: string,\n mentions: ChatMention[],\n ) => void;\n\n /**\n * Invoked when the very first message is sent in a topic that has no name yet — the host\n * asks its LLM (or whatever it prefers) for a good name and supplies it through the topic\n * data; until then the topic renders its pending placeholder. Not invoked again for later\n * messages, and never for a topic that already has a name.\n * @param topic The topic that needs a name.\n * @param firstMessageBody The message to derive the name from.\n */\n onRequestTopicName?: (topic: TTopic, firstMessageBody: string) => void;\n\n /**\n * Decides whether a topic counts as unnamed — see {@link ChatTopicListProps.isTopicUnnamed}.\n * @param topic The topic to check.\n * @returns True when the topic has no usable name yet.\n */\n isTopicUnnamed?: (topic: TTopic) => boolean;\n\n /** Which edge the sidebar opens from. Defaults to `'right'`. */\n position?: 'left' | 'right';\n\n /** The sidebar's width, any CSS length. Defaults to `'24rem'`. */\n width?: string;\n\n /**\n * Whether the sidebar is modal — a backdrop behind it, and dismissing by Escape or clicking\n * outside. Defaults to false: a chat lives *next to* the work, so it should not block it —\n * the background stays visible and interactive, and only the close/back affordances dismiss it.\n */\n modal?: boolean;\n\n /** Overrides for every label rendered. Unset fields fall back to literal English defaults. */\n labels?: ChatSidebarLabels;\n\n /** Additional class name for the sidebar's panel element. */\n className?: string;\n\n /** Cratis-owned per-part attributes. */\n pt?: ChatSidebarParts;\n}\n\nconst classNames = (...values: Array<string | undefined>) =>\n values.filter(Boolean).join(' ');\n\nconst BackIcon = () => (\n <svg\n width='14'\n height='14'\n viewBox='0 0 24 24'\n fill='none'\n stroke='currentColor'\n strokeWidth='2.5'\n aria-hidden='true'\n >\n <line x1='19' y1='12' x2='5' y2='12' />\n <polyline points='12 19 5 12 12 5' />\n </svg>\n);\n\n/**\n * A topic-based chat in a sidebar next to the view, built on Components' own overlay primitives\n * (React Aria internally, for accessible dismissal and focus handling). It opens on the topic\n * list; picking a topic (or starting a new one) slides into that conversation, with a back\n * affordance to the list. Everything about *data* is the host's: topics and messages come in as\n * arrays (hand it live query data and it stays current), sends and new topics go out as\n * callbacks, and when the first message lands in an unnamed topic the sidebar asks the host for\n * a name through {@link ChatSidebarProps.onRequestTopicName | onRequestTopicName} — rendering a\n * pending placeholder until the name arrives.\n */\nexport const ChatSidebar = <\n TMessage extends ChatMessage = ChatMessage,\n TTopic extends ChatTopic = ChatTopic,\n>({\n open,\n onClose,\n topics,\n messages,\n topicsStatus,\n messagesStatus,\n selectedTopicId,\n onTopicSelected,\n onStartTopic,\n onSendMessage,\n onRequestTopicName,\n isTopicUnnamed,\n position = 'right',\n width = '24rem',\n modal = false,\n labels,\n className,\n pt,\n ...conversation\n}: ChatSidebarProps<TMessage, TTopic>) => {\n const icon = useCratisIcon();\n const overlayEnvironment = unstable_useOverlayEnvironment();\n const [mounted, setMounted] = useState(false);\n const nonModalPanel = useRef<HTMLDivElement>(null);\n const [nonModalPhase, setNonModalPhase] = useState<\n 'closed' | 'entering' | 'open' | 'exiting'\n >('closed');\n\n useEffect(() => setMounted(true), []);\n useEffect(() => {\n setNonModalPhase((phase) =>\n open ? 'entering' : phase === 'closed' ? 'closed' : 'exiting',\n );\n }, [open]);\n const portalContainer = !modal && mounted && nonModalPhase !== 'closed'\n ? overlayEnvironment.getContainer()\n : null;\n useEffect(() => {\n if (nonModalPhase !== 'entering' && nonModalPhase !== 'exiting') return;\n if (!modal && (\n window.matchMedia?.('(prefers-reduced-motion: reduce)').matches ||\n nonModalPanel.current?.getAnimations?.().length === 0\n )) {\n setNonModalPhase(nonModalPhase === 'entering' ? 'open' : 'closed');\n return;\n }\n const timeout = setTimeout(() => {\n setNonModalPhase(nonModalPhase === 'entering' ? 'open' : 'closed');\n }, 250);\n return () => clearTimeout(timeout);\n }, [modal, nonModalPhase, portalContainer]);\n\n const [internalSelectedId, setInternalSelectedId] = useState<\n ChatIdentifier | undefined\n >();\n const isControlled = selectedTopicId !== undefined;\n const openTopicId = isControlled\n ? (selectedTopicId ?? undefined)\n : internalSelectedId;\n const openTopic =\n openTopicId === undefined\n ? undefined\n : topics.find((topic) => sameChatIdentifier(topic.id, openTopicId));\n const unnamed = isTopicUnnamed ?? defaultIsTopicUnnamed;\n\n const select = (topicId: ChatIdentifier | undefined, topic?: TTopic) => {\n if (!isControlled) {\n setInternalSelectedId(topicId);\n }\n onTopicSelected?.(\n topicId,\n topic ??\n (topicId === undefined\n ? undefined\n : topics.find((candidate) =>\n sameChatIdentifier(candidate.id, topicId),\n )),\n );\n };\n\n const startTopic = async () => {\n const topicId = await onStartTopic?.();\n if (topicId !== undefined) {\n select(topicId);\n }\n };\n\n const openMessages =\n openTopicId === undefined\n ? []\n : messages.filter((message) =>\n sameChatIdentifier(message.topicId, openTopicId),\n );\n\n const send = (body: string, mentions: ChatMention[]) => {\n if (openTopicId === undefined) return;\n onSendMessage(openTopicId, body, mentions);\n if (\n openTopic &&\n shouldRequestTopicName(unnamed(openTopic), openMessages.length)\n ) {\n onRequestTopicName?.(openTopic, body);\n }\n };\n\n const title =\n openTopicId === undefined\n ? (labels?.topics ?? 'Topics')\n : openTopic && !unnamed(openTopic)\n ? openTopic.name\n : (labels?.unnamedTopic ?? 'New topic');\n const titleIsPending =\n openTopicId !== undefined && (!openTopic || unnamed(openTopic));\n\n const panel = (\n <>\n <header\n {...pt?.header}\n className={classNames(\n 'cratis-chat-sidebar__header',\n pt?.header?.className,\n )}\n data-cratis-part='header'\n >\n {openTopicId !== undefined && (\n <button\n {...pt?.back}\n type='button'\n className={classNames(\n 'cratis-chat-sidebar__back',\n pt?.back?.className,\n )}\n data-cratis-part='back'\n title={labels?.back ?? 'Back to topics'}\n aria-label={labels?.back ?? 'Back to topics'}\n onClick={() => select(undefined)}\n >\n <BackIcon />\n </button>\n )}\n <h2\n {...pt?.title}\n className={classNames(\n 'cratis-chat-sidebar__title',\n titleIsPending\n ? 'cratis-chat-sidebar__title--pending'\n : undefined,\n pt?.title?.className,\n )}\n data-cratis-part='title'\n >\n {title}\n </h2>\n <button\n {...pt?.close}\n type='button'\n className={classNames(\n 'cratis-chat-sidebar__close',\n pt?.close?.className,\n )}\n data-cratis-part='close'\n aria-label={labels?.close ?? 'Close chat'}\n onClick={onClose}\n >\n <span aria-hidden='true'>{icon('close', '×')}</span>\n </button>\n </header>\n <div\n {...pt?.content}\n className={classNames(\n 'cratis-chat-sidebar__content',\n pt?.content?.className,\n )}\n data-cratis-part='content'\n >\n {openTopicId === undefined ? (\n <ChatTopicList<TTopic>\n topics={topics}\n status={topicsStatus}\n onOpen={(topic) => select(topic.id, topic)}\n onStart={\n onStartTopic\n ? () => {\n void startTopic();\n }\n : undefined\n }\n authorOf={conversation.authorOf}\n renderAvatar={conversation.renderAvatar}\n isTopicUnnamed={isTopicUnnamed}\n buildAvatarUrl={conversation.buildAvatarUrl}\n labels={{\n unnamedTopic: labels?.unnamedTopic,\n ...labels?.topicList,\n }}\n />\n ) : (\n <ChatConversation<TMessage>\n {...conversation}\n messages={openMessages}\n status={messagesStatus}\n onSendMessage={send}\n labels={labels?.conversation}\n />\n )}\n </div>\n </>\n );\n\n return modal ? (\n <ModalOverlay\n {...pt?.backdrop}\n isOpen={open}\n onOpenChange={(openState) => {\n if (!openState) onClose();\n }}\n isDismissable={modal}\n isKeyboardDismissDisabled={!modal}\n className={classNames(\n 'cratis-chat-sidebar__backdrop',\n pt?.backdrop?.className,\n )}\n data-cratis-part='backdrop'\n data-modal={modal}\n data-open={open || undefined}\n data-selected={openTopicId !== undefined || undefined}\n >\n <Modal\n {...pt?.root}\n className={classNames(\n 'cratis-chat-sidebar',\n pt?.root?.className,\n className,\n )}\n style={{ width, ...pt?.root?.style }}\n data-cratis-part='root'\n data-position={position}\n data-open={open || undefined}\n data-selected={openTopicId !== undefined || undefined}\n >\n {panel}\n </Modal>\n </ModalOverlay>\n ) : (\n // Non-modal: a chat lives *next to* the work, so the background must stay visible and\n // interactive. React Aria's Modal blocks its backdrop by design (its dismissal props only\n // gate how it closes, never whether it intercepts), so the default is a plain portaled\n // layer with no dismissal behavior at all — only the close/back affordances dismiss it.\n nonModalPhase !== 'closed' &&\n portalContainer &&\n createPortal(\n <div\n {...pt?.backdrop}\n className={classNames(\n 'cratis-chat-sidebar__backdrop',\n pt?.backdrop?.className,\n )}\n data-cratis-part='backdrop'\n data-modal={false}\n data-open={open || undefined}\n data-selected={openTopicId !== undefined || undefined}\n data-entering={nonModalPhase === 'entering' || undefined}\n data-exiting={nonModalPhase === 'exiting' || undefined}\n >\n <div\n {...pt?.root}\n ref={nonModalPanel}\n className={classNames(\n 'cratis-chat-sidebar',\n pt?.root?.className,\n className,\n )}\n style={{ width, ...pt?.root?.style }}\n data-cratis-part='root'\n data-position={position}\n data-open={open || undefined}\n data-selected={openTopicId !== undefined || undefined}\n data-entering={nonModalPhase === 'entering' || undefined}\n data-exiting={nonModalPhase === 'exiting' || undefined}\n onAnimationEnd={(event) => {\n pt?.root?.onAnimationEnd?.(event);\n if (event.target !== event.currentTarget) return;\n setNonModalPhase((phase) =>\n phase === 'entering' ? 'open' : phase === 'exiting' ? 'closed' : phase,\n );\n }}\n >\n {panel}\n </div>\n </div>,\n portalContainer,\n )\n );\n};\n"],"names":["_jsxs","_jsx","isTopicUnnamed","defaultIsTopicUnnamed","_Fragment"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ChatSidebar.js","sources":["../../../Chat/ChatSidebar.tsx"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { useEffect, useRef, useState } from 'react';\nimport type { ButtonHTMLAttributes, HTMLAttributes } from 'react';\nimport { createPortal } from 'react-dom';\nimport { Modal, ModalOverlay } from 'react-aria-components';\nimport type { ChatConversationLabels, ChatConversationProps } from './ChatConversation';\nimport { ChatConversation } from './ChatConversation';\nimport { sameChatIdentifier, type ChatIdentifier } from './ChatIdentifier';\nimport type { ChatMention } from './ChatMention';\nimport type { ChatMessage } from './ChatMessage';\nimport type { ChatTopic } from './ChatTopic';\nimport type { ChatTopicAction } from './ChatTopicAction';\nimport type { ChatStatus } from './ChatStatus';\nimport type { ChatTopicListLabels } from './ChatTopicList';\nimport { ChatTopicList } from './ChatTopicList';\nimport { isTopicUnnamed as defaultIsTopicUnnamed } from './isTopicUnnamed';\nimport { shouldRequestTopicName } from './shouldRequestTopicName';\nimport { useCratisIcon } from '../configuration/useCratisIcon';\nimport { unstable_useOverlayEnvironment } from '../renderer/RendererContext';\nimport type { ExactPartKeys } from '../types/ExactPartKeys';\nimport type { PartsOf } from '../types/parts';\n\ntype ChatSidebarPartAttributes<TElement> = HTMLAttributes<TElement> & {\n [attribute: `data-${string}`]: string | number | boolean | undefined;\n};\n\ntype ChatSidebarButtonAttributes = Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n 'onClick' | 'type'\n>;\n\n/** Stable Cratis-owned parts for styling a {@link ChatSidebar}. */\nexport interface ChatSidebarParts {\n /** Viewport overlay layer the sidebar portals into — only paints/blocks when {@link ChatSidebarProps.modal} is true. */\n backdrop?: ChatSidebarPartAttributes<HTMLDivElement>;\n /** The sidebar panel itself. */\n root?: ChatSidebarPartAttributes<HTMLDivElement>;\n /** Header containing the back affordance, title, and close button. */\n header?: ChatSidebarPartAttributes<HTMLElement>;\n /** The topic list title, or the open topic's name. */\n title?: ChatSidebarPartAttributes<HTMLHeadingElement>;\n /** Back-to-topics button, present only while a topic is open. */\n back?: ChatSidebarButtonAttributes;\n /** Close button. */\n close?: ChatSidebarButtonAttributes;\n /** The topic list / conversation content region. */\n content?: ChatSidebarPartAttributes<HTMLDivElement>;\n}\n\nconst chatSidebarPartsMatchManifest: ExactPartKeys<\n ChatSidebarParts,\n PartsOf<'ChatSidebar'>\n> = true;\nvoid chatSidebarPartsMatchManifest;\n\n/** Overrides for every label the {@link ChatSidebar} and its children render. Any field left\n * unset falls back to a literal English default. */\nexport interface ChatSidebarLabels {\n /** The sidebar's title while the topic list is showing. Defaults to `'Topics'`. */\n topics?: string;\n\n /** Placeholder title for a topic that has not been named yet. Defaults to `'New topic'`. */\n unnamedTopic?: string;\n\n /** Tooltip/accessible label for the back-to-topics button. Defaults to `'Back to topics'`. */\n back?: string;\n\n /** Accessible label for the close button. Defaults to `'Close chat'`. */\n close?: string;\n\n /** Labels forwarded to the {@link ChatTopicList}. */\n topicList?: ChatTopicListLabels;\n\n /** Labels forwarded to the {@link ChatConversation}. */\n conversation?: ChatConversationLabels;\n}\n\n/**\n * Props for {@link ChatSidebar}.\n * @typeParam TMessage The host's message type — anything extending {@link ChatMessage}.\n * @typeParam TTopic The host's topic type — anything extending {@link ChatTopic}.\n */\nexport interface ChatSidebarProps<\n TMessage extends ChatMessage = ChatMessage,\n TTopic extends ChatTopic = ChatTopic,\n> extends Omit<\n ChatConversationProps<TMessage>,\n 'messages' | 'onSendMessage' | 'labels' | 'className' | 'status'\n> {\n /** Whether the sidebar is open. */\n open: boolean;\n\n /** Invoked when the sidebar asks to close — the host owns {@link open}, so closing means the host flips it. */\n onClose: () => void;\n\n /**\n * The topics, in any order. Hand this the array a live (observable) query delivers and the\n * sidebar re-renders as it changes.\n */\n topics: TTopic[];\n\n /**\n * The messages — either the whole chat or just the open topic's; the sidebar shows those of\n * the open topic by matching on `topicId` either way. Hand this the array a live\n * (observable) query delivers and the conversation re-renders as it changes.\n */\n messages: TMessage[];\n\n /** The host's actions to offer on each topic where they are available. The inherited `actions` prop applies only to messages. */\n topicActions?: ChatTopicAction<TTopic>[];\n\n /** Topic-list query display state. Defaults to ready when omitted. */\n topicsStatus?: ChatStatus;\n\n /** Conversation query display state. Defaults to ready when omitted. */\n messagesStatus?: ChatStatus;\n\n /**\n * The open topic, for hosts that own the selection themselves: an identifier opens that\n * topic's conversation, `null` shows the topic list, and leaving the prop unset lets the\n * sidebar keep the selection internally.\n */\n selectedTopicId?: ChatIdentifier | null;\n\n /**\n * Invoked when the open topic changes — a topic was opened (or just started), or the person\n * went back to the list.\n * @param topicId The identifier of the topic now open, or undefined for the list.\n * @param topic The topic itself, when it is known in {@link topics}.\n */\n onTopicSelected?: (topicId: ChatIdentifier | undefined, topic?: TTopic) => void;\n\n /**\n * Invoked when a new topic is asked for. Creating the topic is the host's business; answer\n * with its identifier (directly or through a promise) and the sidebar opens it, ready for\n * the first message. Omit to leave the new-topic affordance off.\n * @returns The identifier of the topic that was started, when the host created one.\n */\n onStartTopic?: () => ChatIdentifier | undefined | Promise<ChatIdentifier | undefined>;\n\n /**\n * Invoked when a message is sent, with the topic it belongs to, the trimmed body and who it\n * mentions. Everything else about persisting the message is the host's business.\n * @param topicId The identifier of the topic the message was sent in.\n * @param body The message text, mentions written into it as plain `@Name`.\n * @param mentions Who the body mentions.\n */\n onSendMessage: (\n topicId: ChatIdentifier,\n body: string,\n mentions: ChatMention[],\n ) => void;\n\n /**\n * Invoked when the very first message is sent in a topic that has no name yet — the host\n * asks its LLM (or whatever it prefers) for a good name and supplies it through the topic\n * data; until then the topic renders its pending placeholder. Not invoked again for later\n * messages, and never for a topic that already has a name.\n * @param topic The topic that needs a name.\n * @param firstMessageBody The message to derive the name from.\n */\n onRequestTopicName?: (topic: TTopic, firstMessageBody: string) => void;\n\n /**\n * Decides whether a topic counts as unnamed — see {@link ChatTopicListProps.isTopicUnnamed}.\n * @param topic The topic to check.\n * @returns True when the topic has no usable name yet.\n */\n isTopicUnnamed?: (topic: TTopic) => boolean;\n\n /** Which edge the sidebar opens from. Defaults to `'right'`. */\n position?: 'left' | 'right';\n\n /** The sidebar's width, any CSS length. Defaults to `'24rem'`. */\n width?: string;\n\n /**\n * Whether the sidebar is modal — a backdrop behind it, and dismissing by Escape or clicking\n * outside. Defaults to false: a chat lives *next to* the work, so it should not block it —\n * the background stays visible and interactive, and only the close/back affordances dismiss it.\n */\n modal?: boolean;\n\n /** Overrides for every label rendered. Unset fields fall back to literal English defaults. */\n labels?: ChatSidebarLabels;\n\n /** Additional class name for the sidebar's panel element. */\n className?: string;\n\n /** Cratis-owned per-part attributes. */\n pt?: ChatSidebarParts;\n}\n\nconst classNames = (...values: Array<string | undefined>) =>\n values.filter(Boolean).join(' ');\n\nconst BackIcon = () => (\n <svg\n width='14'\n height='14'\n viewBox='0 0 24 24'\n fill='none'\n stroke='currentColor'\n strokeWidth='2.5'\n aria-hidden='true'\n >\n <line x1='19' y1='12' x2='5' y2='12' />\n <polyline points='12 19 5 12 12 5' />\n </svg>\n);\n\n/**\n * A topic-based chat in a sidebar next to the view, built on Components' own overlay primitives\n * (React Aria internally, for accessible dismissal and focus handling). It opens on the topic\n * list; picking a topic (or starting a new one) slides into that conversation, with a back\n * affordance to the list. Everything about *data* is the host's: topics and messages come in as\n * arrays (hand it live query data and it stays current), sends and new topics go out as\n * callbacks, and when the first message lands in an unnamed topic the sidebar asks the host for\n * a name through {@link ChatSidebarProps.onRequestTopicName | onRequestTopicName} — rendering a\n * pending placeholder until the name arrives.\n */\nexport const ChatSidebar = <\n TMessage extends ChatMessage = ChatMessage,\n TTopic extends ChatTopic = ChatTopic,\n>({\n open,\n onClose,\n topics,\n messages,\n topicActions,\n topicsStatus,\n messagesStatus,\n selectedTopicId,\n onTopicSelected,\n onStartTopic,\n onSendMessage,\n onRequestTopicName,\n isTopicUnnamed,\n position = 'right',\n width = '24rem',\n modal = false,\n labels,\n className,\n pt,\n ...conversation\n}: ChatSidebarProps<TMessage, TTopic>) => {\n const icon = useCratisIcon();\n const overlayEnvironment = unstable_useOverlayEnvironment();\n const [mounted, setMounted] = useState(false);\n const nonModalPanel = useRef<HTMLDivElement>(null);\n const [nonModalPhase, setNonModalPhase] = useState<\n 'closed' | 'entering' | 'open' | 'exiting'\n >('closed');\n\n useEffect(() => setMounted(true), []);\n useEffect(() => {\n setNonModalPhase((phase) =>\n open ? 'entering' : phase === 'closed' ? 'closed' : 'exiting',\n );\n }, [open]);\n const portalContainer = !modal && mounted && nonModalPhase !== 'closed'\n ? overlayEnvironment.getContainer()\n : null;\n useEffect(() => {\n if (nonModalPhase !== 'entering' && nonModalPhase !== 'exiting') return;\n if (!modal && (\n window.matchMedia?.('(prefers-reduced-motion: reduce)').matches ||\n nonModalPanel.current?.getAnimations?.().length === 0\n )) {\n setNonModalPhase(nonModalPhase === 'entering' ? 'open' : 'closed');\n return;\n }\n const timeout = setTimeout(() => {\n setNonModalPhase(nonModalPhase === 'entering' ? 'open' : 'closed');\n }, 250);\n return () => clearTimeout(timeout);\n }, [modal, nonModalPhase, portalContainer]);\n\n const [internalSelectedId, setInternalSelectedId] = useState<\n ChatIdentifier | undefined\n >();\n const isControlled = selectedTopicId !== undefined;\n const openTopicId = isControlled\n ? (selectedTopicId ?? undefined)\n : internalSelectedId;\n const openTopic =\n openTopicId === undefined\n ? undefined\n : topics.find((topic) => sameChatIdentifier(topic.id, openTopicId));\n const unnamed = isTopicUnnamed ?? defaultIsTopicUnnamed;\n\n const select = (topicId: ChatIdentifier | undefined, topic?: TTopic) => {\n if (!isControlled) {\n setInternalSelectedId(topicId);\n }\n onTopicSelected?.(\n topicId,\n topic ??\n (topicId === undefined\n ? undefined\n : topics.find((candidate) =>\n sameChatIdentifier(candidate.id, topicId),\n )),\n );\n };\n\n const startTopic = async () => {\n const topicId = await onStartTopic?.();\n if (topicId !== undefined) {\n select(topicId);\n }\n };\n\n const openMessages =\n openTopicId === undefined\n ? []\n : messages.filter((message) =>\n sameChatIdentifier(message.topicId, openTopicId),\n );\n\n const send = (body: string, mentions: ChatMention[]) => {\n if (openTopicId === undefined) return;\n onSendMessage(openTopicId, body, mentions);\n if (\n openTopic &&\n shouldRequestTopicName(unnamed(openTopic), openMessages.length)\n ) {\n onRequestTopicName?.(openTopic, body);\n }\n };\n\n const title =\n openTopicId === undefined\n ? (labels?.topics ?? 'Topics')\n : openTopic && !unnamed(openTopic)\n ? openTopic.name\n : (labels?.unnamedTopic ?? 'New topic');\n const titleIsPending =\n openTopicId !== undefined && (!openTopic || unnamed(openTopic));\n\n const panel = (\n <>\n <header\n {...pt?.header}\n className={classNames(\n 'cratis-chat-sidebar__header',\n pt?.header?.className,\n )}\n data-cratis-part='header'\n >\n {openTopicId !== undefined && (\n <button\n {...pt?.back}\n type='button'\n className={classNames(\n 'cratis-chat-sidebar__back',\n pt?.back?.className,\n )}\n data-cratis-part='back'\n title={labels?.back ?? 'Back to topics'}\n aria-label={labels?.back ?? 'Back to topics'}\n onClick={() => select(undefined)}\n >\n <BackIcon />\n </button>\n )}\n <h2\n {...pt?.title}\n className={classNames(\n 'cratis-chat-sidebar__title',\n titleIsPending\n ? 'cratis-chat-sidebar__title--pending'\n : undefined,\n pt?.title?.className,\n )}\n data-cratis-part='title'\n >\n {title}\n </h2>\n <button\n {...pt?.close}\n type='button'\n className={classNames(\n 'cratis-chat-sidebar__close',\n pt?.close?.className,\n )}\n data-cratis-part='close'\n aria-label={labels?.close ?? 'Close chat'}\n onClick={onClose}\n >\n <span aria-hidden='true'>{icon('close', '×')}</span>\n </button>\n </header>\n <div\n {...pt?.content}\n className={classNames(\n 'cratis-chat-sidebar__content',\n pt?.content?.className,\n )}\n data-cratis-part='content'\n >\n {openTopicId === undefined ? (\n <ChatTopicList<TTopic>\n topics={topics}\n topicActions={topicActions}\n status={topicsStatus}\n onOpen={(topic) => select(topic.id, topic)}\n onStart={\n onStartTopic\n ? () => {\n void startTopic();\n }\n : undefined\n }\n authorOf={conversation.authorOf}\n renderAvatar={conversation.renderAvatar}\n isTopicUnnamed={isTopicUnnamed}\n buildAvatarUrl={conversation.buildAvatarUrl}\n labels={{\n unnamedTopic: labels?.unnamedTopic,\n ...labels?.topicList,\n }}\n />\n ) : (\n <ChatConversation<TMessage>\n {...conversation}\n messages={openMessages}\n status={messagesStatus}\n onSendMessage={send}\n labels={labels?.conversation}\n />\n )}\n </div>\n </>\n );\n\n return modal ? (\n <ModalOverlay\n {...pt?.backdrop}\n isOpen={open}\n onOpenChange={(openState) => {\n if (!openState) onClose();\n }}\n isDismissable={modal}\n isKeyboardDismissDisabled={!modal}\n className={classNames(\n 'cratis-chat-sidebar__backdrop',\n pt?.backdrop?.className,\n )}\n data-cratis-part='backdrop'\n data-modal={modal}\n data-open={open || undefined}\n data-selected={openTopicId !== undefined || undefined}\n >\n <Modal\n {...pt?.root}\n className={classNames(\n 'cratis-chat-sidebar',\n pt?.root?.className,\n className,\n )}\n style={{ width, ...pt?.root?.style }}\n data-cratis-part='root'\n data-position={position}\n data-open={open || undefined}\n data-selected={openTopicId !== undefined || undefined}\n >\n {panel}\n </Modal>\n </ModalOverlay>\n ) : (\n // Non-modal: a chat lives *next to* the work, so the background must stay visible and\n // interactive. React Aria's Modal blocks its backdrop by design (its dismissal props only\n // gate how it closes, never whether it intercepts), so the default is a plain portaled\n // layer with no dismissal behavior at all — only the close/back affordances dismiss it.\n nonModalPhase !== 'closed' &&\n portalContainer &&\n createPortal(\n <div\n {...pt?.backdrop}\n className={classNames(\n 'cratis-chat-sidebar__backdrop',\n pt?.backdrop?.className,\n )}\n data-cratis-part='backdrop'\n data-modal={false}\n data-open={open || undefined}\n data-selected={openTopicId !== undefined || undefined}\n data-entering={nonModalPhase === 'entering' || undefined}\n data-exiting={nonModalPhase === 'exiting' || undefined}\n >\n <div\n {...pt?.root}\n ref={nonModalPanel}\n className={classNames(\n 'cratis-chat-sidebar',\n pt?.root?.className,\n className,\n )}\n style={{ width, ...pt?.root?.style }}\n data-cratis-part='root'\n data-position={position}\n data-open={open || undefined}\n data-selected={openTopicId !== undefined || undefined}\n data-entering={nonModalPhase === 'entering' || undefined}\n data-exiting={nonModalPhase === 'exiting' || undefined}\n onAnimationEnd={(event) => {\n pt?.root?.onAnimationEnd?.(event);\n if (event.target !== event.currentTarget) return;\n setNonModalPhase((phase) =>\n phase === 'entering' ? 'open' : phase === 'exiting' ? 'closed' : phase,\n );\n }}\n >\n {panel}\n </div>\n </div>,\n portalContainer,\n )\n );\n};\n"],"names":["_jsxs","_jsx","isTopicUnnamed","defaultIsTopicUnnamed","_Fragment"],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { ChatTopic } from './ChatTopic.js';
|
|
3
|
+
/** An action a host offers on topics. The library ships no topic actions of its own.
|
|
4
|
+
* @typeParam TTopic The host's topic type, so {@link onInvoke} receives the full topic back.
|
|
5
|
+
*/
|
|
6
|
+
export interface ChatTopicAction<TTopic extends ChatTopic = ChatTopic> {
|
|
7
|
+
/** Identifies the action among its siblings — used as the rendering key. */
|
|
8
|
+
id: string;
|
|
9
|
+
/** The label, used in the tooltip and accessible name alongside the topic's name. */
|
|
10
|
+
label: string;
|
|
11
|
+
/** An icon element or a CSS class name for the host's icon font. Omit to show the label. */
|
|
12
|
+
icon?: string | ReactNode;
|
|
13
|
+
/** Decides which topics the action is offered on. Omit to offer it on all topics.
|
|
14
|
+
* @param topic The topic the action would apply to.
|
|
15
|
+
* @returns True when the action should be offered.
|
|
16
|
+
*/
|
|
17
|
+
isAvailable?: (topic: TTopic) => boolean;
|
|
18
|
+
/** Invoked when the action is picked on a topic.
|
|
19
|
+
* @param topic The topic it was picked on.
|
|
20
|
+
*/
|
|
21
|
+
onInvoke: (topic: TTopic) => void;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=ChatTopicAction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatTopicAction.d.ts","sourceRoot":"","sources":["../../../Chat/ChatTopicAction.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAa,CAAC;AAE7C;;GAEG;AACH,MAAM,WAAW,eAAe,CAAC,MAAM,SAAS,SAAS,GAAG,SAAS;IACjE,4EAA4E;IAC5E,EAAE,EAAE,MAAM,CAAC;IAEX,qFAAqF;IACrF,KAAK,EAAE,MAAM,CAAC;IAEd,4FAA4F;IAC5F,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAE1B;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IAEzC;;OAEG;IACH,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatTopicAction.js","sourceRoot":"","sources":["../../../Chat/ChatTopicAction.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,qGAAqG","sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport type { ReactNode } from 'react';\nimport type { ChatTopic } from './ChatTopic';\n\n/** An action a host offers on topics. The library ships no topic actions of its own.\n * @typeParam TTopic The host's topic type, so {@link onInvoke} receives the full topic back.\n */\nexport interface ChatTopicAction<TTopic extends ChatTopic = ChatTopic> {\n /** Identifies the action among its siblings — used as the rendering key. */\n id: string;\n\n /** The label, used in the tooltip and accessible name alongside the topic's name. */\n label: string;\n\n /** An icon element or a CSS class name for the host's icon font. Omit to show the label. */\n icon?: string | ReactNode;\n\n /** Decides which topics the action is offered on. Omit to offer it on all topics.\n * @param topic The topic the action would apply to.\n * @returns True when the action should be offered.\n */\n isAvailable?: (topic: TTopic) => boolean;\n\n /** Invoked when the action is picked on a topic.\n * @param topic The topic it was picked on.\n */\n onInvoke: (topic: TTopic) => void;\n}\n"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { FocusEventHandler } from 'react';
|
|
2
|
+
import type { ChatTopic } from './ChatTopic.js';
|
|
3
|
+
import type { ChatTopicAction } from './ChatTopicAction.js';
|
|
4
|
+
/**
|
|
5
|
+
* Props for {@link ChatTopicActions}.
|
|
6
|
+
* @typeParam TTopic The topic type the list renders.
|
|
7
|
+
*/
|
|
8
|
+
export interface ChatTopicActionsProps<TTopic extends ChatTopic> {
|
|
9
|
+
/** The topic the actions act on. */
|
|
10
|
+
topic: TTopic;
|
|
11
|
+
/** The topic's visible name, used to give each action an accessible name for its topic. */
|
|
12
|
+
topicName: string | undefined;
|
|
13
|
+
/** The actions available for this topic. */
|
|
14
|
+
actions: ChatTopicAction<TTopic>[];
|
|
15
|
+
/** Called when an action button gains focus. */
|
|
16
|
+
onActionFocus: FocusEventHandler<HTMLButtonElement>;
|
|
17
|
+
/** Called when an action button loses focus. */
|
|
18
|
+
onActionBlur: FocusEventHandler<HTMLButtonElement>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The action buttons shown beside a topic's opening button. Reuses the message action overlay,
|
|
22
|
+
* which keyboard focus reveals.
|
|
23
|
+
*/
|
|
24
|
+
export declare const ChatTopicActions: <TTopic extends ChatTopic>({ topic, topicName, actions, onActionFocus, onActionBlur, }: ChatTopicActionsProps<TTopic>) => import("react").JSX.Element;
|
|
25
|
+
//# sourceMappingURL=ChatTopicActions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatTopicActions.d.ts","sourceRoot":"","sources":["../../../Chat/ChatTopicActions.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAa,CAAC;AAC7C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAmB,CAAC;AAEzD;;;GAGG;AACH,MAAM,WAAW,qBAAqB,CAAC,MAAM,SAAS,SAAS;IAC3D,oCAAoC;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,2FAA2F;IAC3F,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,4CAA4C;IAC5C,OAAO,EAAE,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC;IACnC,gDAAgD;IAChD,aAAa,EAAE,iBAAiB,CAAC,iBAAiB,CAAC,CAAC;IACpD,gDAAgD;IAChD,YAAY,EAAE,iBAAiB,CAAC,iBAAiB,CAAC,CAAC;CACtD;AAED;;;GAGG;AACH,eAAO,MAAM,gBAAgB,GAAI,MAAM,SAAS,SAAS,+DAMtD,qBAAqB,CAAC,MAAM,CAAC,gCAoB/B,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
const ChatTopicActions = ({ topic, topicName, actions, onActionFocus, onActionBlur, }) => (jsx("div", { className: 'cratis-chat-message__actions', children: actions.map((action) => (jsx("button", { type: 'button', className: 'cratis-chat-message__action', style: action.icon == null ? { width: 'auto', padding: '0 0.375rem' } : undefined, title: action.label, "aria-label": `${action.label} ${topicName}`, onFocus: onActionFocus, onBlur: onActionBlur, onClick: () => action.onInvoke(topic), children: typeof action.icon === 'string' ? (jsx("i", { className: action.icon, "aria-hidden": 'true' })) : (action.icon ?? action.label) }, action.id))) }));
|
|
4
|
+
|
|
5
|
+
export { ChatTopicActions };
|
|
6
|
+
//# sourceMappingURL=ChatTopicActions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatTopicActions.js","sources":["../../../Chat/ChatTopicActions.tsx"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport type { FocusEventHandler } from 'react';\nimport type { ChatTopic } from './ChatTopic';\nimport type { ChatTopicAction } from './ChatTopicAction';\n\n/**\n * Props for {@link ChatTopicActions}.\n * @typeParam TTopic The topic type the list renders.\n */\nexport interface ChatTopicActionsProps<TTopic extends ChatTopic> {\n /** The topic the actions act on. */\n topic: TTopic;\n /** The topic's visible name, used to give each action an accessible name for its topic. */\n topicName: string | undefined;\n /** The actions available for this topic. */\n actions: ChatTopicAction<TTopic>[];\n /** Called when an action button gains focus. */\n onActionFocus: FocusEventHandler<HTMLButtonElement>;\n /** Called when an action button loses focus. */\n onActionBlur: FocusEventHandler<HTMLButtonElement>;\n}\n\n/**\n * The action buttons shown beside a topic's opening button. Reuses the message action overlay,\n * which keyboard focus reveals.\n */\nexport const ChatTopicActions = <TTopic extends ChatTopic>({\n topic,\n topicName,\n actions,\n onActionFocus,\n onActionBlur,\n}: ChatTopicActionsProps<TTopic>) => (\n <div className='cratis-chat-message__actions'>\n {actions.map((action) => (\n <button\n key={action.id}\n type='button'\n className='cratis-chat-message__action'\n style={action.icon == null ? { width: 'auto', padding: '0 0.375rem' } : undefined}\n title={action.label}\n aria-label={`${action.label} ${topicName}`}\n onFocus={onActionFocus}\n onBlur={onActionBlur}\n onClick={() => action.onInvoke(topic)}\n >\n {typeof action.icon === 'string' ? (\n <i className={action.icon} aria-hidden='true' />\n ) : (action.icon ?? action.label)}\n </button>\n ))}\n </div>\n);\n"],"names":["_jsx"],"mappings":";;AA4BO,MAAM,gBAAgB,GAAG,CAA2B,EACvD,KAAK,EACL,SAAS,EACT,OAAO,EACP,aAAa,EACb,YAAY,GACgB,MAC5BA,GAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAC,8BAA8B,EAAA,QAAA,EACxC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,MAChBA,GAAA,CAAA,QAAA,EAAA,EAEI,IAAI,EAAC,QAAQ,EACb,SAAS,EAAC,6BAA6B,EACvC,KAAK,EAAE,MAAM,CAAC,IAAI,IAAI,IAAI,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,SAAS,EACjF,KAAK,EAAE,MAAM,CAAC,KAAK,EAAA,YAAA,EACP,CAAA,EAAG,MAAM,CAAC,KAAK,CAAA,CAAA,EAAI,SAAS,CAAA,CAAE,EAC1C,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,MAAM,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAA,QAAA,EAEpC,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,IAC5BA,GAAA,CAAA,GAAA,EAAA,EAAG,SAAS,EAAE,MAAM,CAAC,IAAI,EAAA,aAAA,EAAc,MAAM,EAAA,CAAG,KAC/C,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,KAAK,CAAC,EAAA,EAZ5B,MAAM,CAAC,EAAE,CAaT,CACZ,CAAC,EAAA,CACA;;"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
2
|
import { type BuildAvatarUrlParams } from './Kit/Avatar/index.js';
|
|
3
3
|
import type { ChatAuthor } from './ChatAuthor.js';
|
|
4
4
|
import { type ChatIdentifier } from './ChatIdentifier.js';
|
|
5
5
|
import type { ChatTopic } from './ChatTopic.js';
|
|
6
|
+
import type { ChatTopicAction } from './ChatTopicAction.js';
|
|
6
7
|
import { ChatStatus } from './ChatStatus.js';
|
|
7
8
|
import { type RelativeTimestampLabels } from './relativeTimestamp.js';
|
|
8
9
|
/** Overrides for every label the {@link ChatTopicList} renders. Any field left unset falls back
|
|
@@ -73,6 +74,8 @@ export interface ChatTopicListProps<TTopic extends ChatTopic = ChatTopic> {
|
|
|
73
74
|
buildAvatarUrl?: (params: BuildAvatarUrlParams) => string;
|
|
74
75
|
/** Overrides for every label rendered. Unset fields fall back to literal English defaults. */
|
|
75
76
|
labels?: ChatTopicListLabels;
|
|
77
|
+
/** The host's actions to offer on each topic where they are available. */
|
|
78
|
+
topicActions?: ChatTopicAction<TTopic>[];
|
|
76
79
|
/** Additional class name for the list's root element. */
|
|
77
80
|
className?: string;
|
|
78
81
|
}
|
|
@@ -82,5 +85,5 @@ export interface ChatTopicListProps<TTopic extends ChatTopic = ChatTopic> {
|
|
|
82
85
|
* host-side auto-naming, the name arrives through the data once the host has produced it, and
|
|
83
86
|
* the placeholder simply stops being needed.
|
|
84
87
|
*/
|
|
85
|
-
export declare const ChatTopicList: <TTopic extends ChatTopic = ChatTopic>({ topics, status, onOpen, onStart, authorOf, renderAvatar, isTopicUnnamed, buildAvatarUrl, labels, className, }: ChatTopicListProps<TTopic>) => import("react").JSX.Element;
|
|
88
|
+
export declare const ChatTopicList: <TTopic extends ChatTopic = ChatTopic>({ topics, status, onOpen, onStart, authorOf, renderAvatar, isTopicUnnamed, buildAvatarUrl, labels, topicActions, className, }: ChatTopicListProps<TTopic>) => import("react").JSX.Element;
|
|
86
89
|
//# sourceMappingURL=ChatTopicList.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatTopicList.d.ts","sourceRoot":"","sources":["../../../Chat/ChatTopicList.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"ChatTopicList.d.ts","sourceRoot":"","sources":["../../../Chat/ChatTopicList.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAU,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC/C,OAAO,EAAsB,KAAK,oBAAoB,EAAE,MAAM,uBAAc,CAAC;AAE7E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAc,CAAC;AAC/C,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,qBAAkB,CAAC;AAC7E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAa,CAAC;AAC7C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAmB,CAAC;AAEzD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAc,CAAC;AAE1C,OAAO,EAAqB,KAAK,uBAAuB,EAAE,MAAM,wBAAqB,CAAC;AAmBtF;oCACoC;AACpC,MAAM,WAAW,mBAAmB;IAChC,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,2FAA2F;IAC3F,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,4GAA4G;IAC5G,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,8GAA8G;IAC9G,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,+HAA+H;IAC/H,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,+FAA+F;IAC/F,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,2DAA2D;IAC3D,UAAU,CAAC,EAAE,uBAAuB,CAAC;CACxC;AAED;;;GAGG;AACH,MAAM,WAAW,kBAAkB,CAAC,MAAM,SAAS,SAAS,GAAG,SAAS;IACpE;;;OAGG;IACH,MAAM,EAAE,MAAM,EAAE,CAAC;IAEjB,mKAAmK;IACnK,MAAM,CAAC,EAAE,UAAU,CAAC;IAEpB;;;OAGG;IACH,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAEhC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAErB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,UAAU,CAAC;IAEpD;;;;;OAKG;IACH,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,UAAU,KAAK,SAAS,CAAC;IAE3E;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IAE5C,oFAAoF;IACpF,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,oBAAoB,KAAK,MAAM,CAAC;IAE1D,8FAA8F;IAC9F,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAE7B,0EAA0E;IAC1E,YAAY,CAAC,EAAE,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC;IAEzC,yDAAyD;IACzD,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GAAI,MAAM,SAAS,SAAS,GAAG,SAAS,iIAY/D,kBAAkB,CAAC,MAAM,CAAC,gCAgI5B,CAAC"}
|
|
@@ -1,32 +1,40 @@
|
|
|
1
1
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useRef } from 'react';
|
|
2
3
|
import { PersonAvatarCircle } from './Kit/Avatar/PersonAvatarCircle.js';
|
|
3
4
|
import { ChatAuthorKind } from './Kit/ChatAuthorKind.js';
|
|
4
5
|
import { chatIdentifierString } from './ChatIdentifier.js';
|
|
6
|
+
import { ChatTopicActions } from './ChatTopicActions.js';
|
|
5
7
|
import { ChatStatus } from './ChatStatus.js';
|
|
6
8
|
import { isTopicUnnamed } from './isTopicUnnamed.js';
|
|
7
9
|
import { relativeTimestamp } from './relativeTimestamp.js';
|
|
10
|
+
import { useTopicActionFocusRecovery } from './useTopicActionFocusRecovery.js';
|
|
8
11
|
import { topicsByActivity } from './topicsByActivity.js';
|
|
9
12
|
|
|
10
13
|
const PlusIcon = () => (jsxs("svg", { width: '14', height: '14', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: '2.5', "aria-hidden": 'true', children: [jsx("line", { x1: '12', y1: '5', x2: '12', y2: '19' }), jsx("line", { x1: '5', y1: '12', x2: '19', y2: '12' })] }));
|
|
11
|
-
const ChatTopicList = ({ topics, status = ChatStatus.Ready, onOpen, onStart, authorOf, renderAvatar, isTopicUnnamed: isTopicUnnamed$1, buildAvatarUrl, labels, className, }) => {
|
|
14
|
+
const ChatTopicList = ({ topics, status = ChatStatus.Ready, onOpen, onStart, authorOf, renderAvatar, isTopicUnnamed: isTopicUnnamed$1, buildAvatarUrl, labels, topicActions, className, }) => {
|
|
12
15
|
const unnamed = isTopicUnnamed$1 ?? isTopicUnnamed;
|
|
16
|
+
const listRef = useRef(null);
|
|
17
|
+
const orderedTopics = status === ChatStatus.Unauthorized ? [] : topicsByActivity(topics);
|
|
18
|
+
const { onActionFocus, onActionBlur } = useTopicActionFocusRecovery(listRef, orderedTopics.map((topic) => chatIdentifierString(topic.id)));
|
|
13
19
|
const authorFor = (authorId) => authorOf?.(authorId) ?? {
|
|
14
20
|
name: chatIdentifierString(authorId),
|
|
15
21
|
kind: ChatAuthorKind.User,
|
|
16
22
|
};
|
|
17
|
-
return (jsxs("div", { className: `cratis-chat-topics${className ? ` ${className}` : ''}`, "aria-busy": status === ChatStatus.Loading && topics.length > 0 || undefined, children: [onStart && (jsxs("button", { type: 'button', className: 'cratis-chat-topics__start', disabled: status === ChatStatus.Unauthorized, onClick: onStart, children: [jsx(PlusIcon, {}), jsx("span", { children: labels?.newTopic ?? 'New topic' })] })), (topics.length === 0 || status === ChatStatus.Failed || status === ChatStatus.Unauthorized) && (jsx("p", { className: 'cratis-chat-topics__empty', children: status === ChatStatus.Loading ? (jsx("span", { role: 'status', children: labels?.loading ?? 'Loading topics…' }, status)) : status === ChatStatus.Failed ? (jsx("span", { role: 'alert', children: labels?.failed ?? 'Could not load topics.' }, status)) : status === ChatStatus.Unauthorized ? (jsx("span", { role: 'alert', children: labels?.unauthorized ?? 'You are not authorized to view these topics.' }, status)) : (labels?.empty ?? 'No topics yet. Start the first one!') })), jsx("ul", { className: 'cratis-chat-topics__list', children: status !== ChatStatus.Unauthorized &&
|
|
23
|
+
return (jsxs("div", { className: `cratis-chat-topics${className ? ` ${className}` : ''}`, "aria-busy": status === ChatStatus.Loading && topics.length > 0 || undefined, children: [onStart && (jsxs("button", { type: 'button', className: 'cratis-chat-topics__start', disabled: status === ChatStatus.Unauthorized, onClick: onStart, children: [jsx(PlusIcon, {}), jsx("span", { children: labels?.newTopic ?? 'New topic' })] })), (topics.length === 0 || status === ChatStatus.Failed || status === ChatStatus.Unauthorized) && (jsx("p", { className: 'cratis-chat-topics__empty', children: status === ChatStatus.Loading ? (jsx("span", { role: 'status', children: labels?.loading ?? 'Loading topics…' }, status)) : status === ChatStatus.Failed ? (jsx("span", { role: 'alert', children: labels?.failed ?? 'Could not load topics.' }, status)) : status === ChatStatus.Unauthorized ? (jsx("span", { role: 'alert', children: labels?.unauthorized ?? 'You are not authorized to view these topics.' }, status)) : (labels?.empty ?? 'No topics yet. Start the first one!') })), jsx("ul", { ref: listRef, className: 'cratis-chat-topics__list', children: status !== ChatStatus.Unauthorized && orderedTopics.map((topic) => {
|
|
18
24
|
const starter = topic.startedBy === undefined
|
|
19
25
|
? undefined
|
|
20
26
|
: authorFor(topic.startedBy);
|
|
21
27
|
const isPending = unnamed(topic);
|
|
22
28
|
const activity = topic.lastActivity ?? topic.started;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
const topicName = isPending ? (labels?.unnamedTopic ?? 'New topic') : topic.name;
|
|
30
|
+
const availableActions = (topicActions ?? []).filter((action) => action.isAvailable?.(topic) ?? true);
|
|
31
|
+
const topicButton = (jsxs("button", { type: 'button', className: 'cratis-chat-topics__topic', onClick: () => onOpen(topic), children: [topic.startedBy !== undefined &&
|
|
32
|
+
starter &&
|
|
33
|
+
(renderAvatar ? (renderAvatar(topic.startedBy, starter)) : (jsx(PersonAvatarCircle, { userId: chatIdentifierString(topic.startedBy), name: starter.name, hasAvatar: starter.hasAvatar ?? false, size: 28, ownerType: starter.kind === ChatAuthorKind.Agent
|
|
34
|
+
? 'Agents'
|
|
35
|
+
: 'Users', version: starter.avatarVersion, buildAvatarUrl: buildAvatarUrl }))), jsxs("span", { className: 'cratis-chat-topics__details', children: [jsx("span", { className: `cratis-chat-topics__name${isPending ? ' cratis-chat-topics__name--pending' : ''}`, children: topicName }), starter && (jsx("span", { className: 'cratis-chat-topics__started-by', children: (labels?.startedBy ?? 'Started by {name}').replace('{name}', starter.name) }))] }), activity !== undefined && (jsx("span", { className: 'cratis-chat-topics__activity', children: relativeTimestamp(activity, new Date(), labels?.timestamps) }))] }));
|
|
36
|
+
const topicKey = chatIdentifierString(topic.id);
|
|
37
|
+
return (jsxs("li", { className: availableActions.length > 0 ? 'cratis-chat-topics__row' : undefined, children: [topicButton, availableActions.length > 0 && (jsx(ChatTopicActions, { topic: topic, topicName: topicName, actions: availableActions, onActionFocus: onActionFocus(topicKey), onActionBlur: onActionBlur }))] }, topicKey));
|
|
30
38
|
}) })] }));
|
|
31
39
|
};
|
|
32
40
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatTopicList.js","sources":["../../../Chat/ChatTopicList.tsx"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport type { ReactNode } from 'react';\nimport { PersonAvatarCircle, type BuildAvatarUrlParams } from './Kit/Avatar';\nimport { ChatAuthorKind } from './Kit/ChatAuthorKind';\nimport type { ChatAuthor } from './ChatAuthor';\nimport { chatIdentifierString, type ChatIdentifier } from './ChatIdentifier';\nimport type { ChatTopic } from './ChatTopic';\nimport { ChatStatus } from './ChatStatus';\nimport { isTopicUnnamed as defaultIsTopicUnnamed } from './isTopicUnnamed';\nimport { relativeTimestamp, type RelativeTimestampLabels } from './relativeTimestamp';\nimport { topicsByActivity } from './topicsByActivity';\n\nconst PlusIcon = () => (\n <svg\n width='14'\n height='14'\n viewBox='0 0 24 24'\n fill='none'\n stroke='currentColor'\n strokeWidth='2.5'\n aria-hidden='true'\n >\n <line x1='12' y1='5' x2='12' y2='19' />\n <line x1='5' y1='12' x2='19' y2='12' />\n </svg>\n);\n\n/** Overrides for every label the {@link ChatTopicList} renders. Any field left unset falls back\n * to a literal English default. */\nexport interface ChatTopicListLabels {\n /** The new-topic button. Defaults to `'New topic'`. */\n newTopic?: string;\n\n /** Placeholder name for a topic that has not been named yet. Defaults to `'New topic'`. */\n unnamedTopic?: string;\n\n /** The started-by line under a topic's name. `{name}` is substituted. Defaults to `'Started by {name}'`. */\n startedBy?: string;\n\n /** Shown while loading without topics. Defaults to `'Loading topics…'`. */\n loading?: string;\n\n /** Shown when loading topics fails, alongside any existing topics. Defaults to `'Could not load topics.'`. */\n failed?: string;\n\n /** Shown when access is denied; replaces any existing topics. Defaults to `'You are not authorized to view these topics.'`. */\n unauthorized?: string;\n\n /** Shown when there are no topics yet. Defaults to `'No topics yet. Start the first one!'`. */\n empty?: string;\n\n /** Overrides for the relative last-activity timestamps. */\n timestamps?: RelativeTimestampLabels;\n}\n\n/**\n * Props for {@link ChatTopicList}.\n * @typeParam TTopic The host's topic type — anything extending {@link ChatTopic}.\n */\nexport interface ChatTopicListProps<TTopic extends ChatTopic = ChatTopic> {\n /**\n * The topics, in any order — the list orders them by most recent activity itself. Hand this\n * the array a live (observable) query delivers and the list re-renders as it changes.\n */\n topics: TTopic[];\n\n /** Query display state. Defaults to {@link ChatStatus.Ready}; existing topics remain visible on loading or failure, and unauthorized disables starting a topic. */\n status?: ChatStatus;\n\n /**\n * Invoked when a topic is picked from the list.\n * @param topic The topic that was picked.\n */\n onOpen: (topic: TTopic) => void;\n\n /**\n * Invoked when a new topic is asked for. Creating the topic is the host's business — the\n * component only raises the intent. Omit to leave the new-topic affordance off.\n */\n onStart?: () => void;\n\n /**\n * Resolves an author id into what it takes to render whoever started a topic. Omit to fall\n * back to rendering the id itself as the name.\n * @param authorId The identifier of the author to resolve.\n * @returns The author.\n */\n authorOf?: (authorId: ChatIdentifier) => ChatAuthor;\n\n /**\n * Renders the avatar for whoever started a topic, replacing the built-in initials circle.\n * @param authorId The identifier of the author.\n * @param author The resolved author, as {@link authorOf} answered.\n * @returns What to render.\n */\n renderAvatar?: (authorId: ChatIdentifier, author: ChatAuthor) => ReactNode;\n\n /**\n * Decides whether a topic counts as unnamed and renders the pending placeholder. Defaults to\n * \"no name, or only whitespace\" — override when the host's backend stores a literal\n * placeholder for unnamed topics instead.\n * @param topic The topic to check.\n * @returns True when the topic has no usable name yet.\n */\n isTopicUnnamed?: (topic: TTopic) => boolean;\n\n /** Builds the avatar image URL for topic starters. Omit to always show initials. */\n buildAvatarUrl?: (params: BuildAvatarUrlParams) => string;\n\n /** Overrides for every label rendered. Unset fields fall back to literal English defaults. */\n labels?: ChatTopicListLabels;\n\n /** Additional class name for the list's root element. */\n className?: string;\n}\n\n/**\n * The topics of a chat, most recently active first — pick one to open its conversation, or start\n * a new one. A topic that has not been named yet renders a placeholder in a pending style: with\n * host-side auto-naming, the name arrives through the data once the host has produced it, and\n * the placeholder simply stops being needed.\n */\nexport const ChatTopicList = <TTopic extends ChatTopic = ChatTopic>({\n topics,\n status = ChatStatus.Ready,\n onOpen,\n onStart,\n authorOf,\n renderAvatar,\n isTopicUnnamed,\n buildAvatarUrl,\n labels,\n className,\n}: ChatTopicListProps<TTopic>) => {\n const unnamed = isTopicUnnamed ?? defaultIsTopicUnnamed;\n\n const authorFor = (authorId: ChatIdentifier): ChatAuthor =>\n authorOf?.(authorId) ?? {\n name: chatIdentifierString(authorId),\n kind: ChatAuthorKind.User,\n };\n\n return (\n <div className={`cratis-chat-topics${className ? ` ${className}` : ''}`} aria-busy={status === ChatStatus.Loading && topics.length > 0 || undefined}>\n {onStart && (\n <button\n type='button'\n className='cratis-chat-topics__start'\n disabled={status === ChatStatus.Unauthorized}\n onClick={onStart}\n >\n <PlusIcon />\n <span>{labels?.newTopic ?? 'New topic'}</span>\n </button>\n )}\n {(topics.length === 0 || status === ChatStatus.Failed || status === ChatStatus.Unauthorized) && (\n <p className='cratis-chat-topics__empty'>\n {status === ChatStatus.Loading ? (\n <span key={status} role='status'>{labels?.loading ?? 'Loading topics…'}</span>\n ) : status === ChatStatus.Failed ? (\n <span key={status} role='alert'>{labels?.failed ?? 'Could not load topics.'}</span>\n ) : status === ChatStatus.Unauthorized ? (\n <span key={status} role='alert'>{labels?.unauthorized ?? 'You are not authorized to view these topics.'}</span>\n ) : (\n labels?.empty ?? 'No topics yet. Start the first one!'\n )}\n </p>\n )}\n <ul className='cratis-chat-topics__list'>\n {status !== ChatStatus.Unauthorized && topicsByActivity(topics).map((topic) => {\n const starter =\n topic.startedBy === undefined\n ? undefined\n : authorFor(topic.startedBy);\n const isPending = unnamed(topic);\n const activity = topic.lastActivity ?? topic.started;\n\n return (\n <li key={chatIdentifierString(topic.id)}>\n <button\n type='button'\n className='cratis-chat-topics__topic'\n onClick={() => onOpen(topic)}\n >\n {topic.startedBy !== undefined &&\n starter &&\n (renderAvatar ? (\n renderAvatar(topic.startedBy, starter)\n ) : (\n <PersonAvatarCircle\n userId={chatIdentifierString(topic.startedBy)}\n name={starter.name}\n hasAvatar={starter.hasAvatar ?? false}\n size={28}\n ownerType={\n starter.kind === ChatAuthorKind.Agent\n ? 'Agents'\n : 'Users'\n }\n version={starter.avatarVersion}\n buildAvatarUrl={buildAvatarUrl}\n />\n ))}\n <span className='cratis-chat-topics__details'>\n <span\n className={`cratis-chat-topics__name${isPending ? ' cratis-chat-topics__name--pending' : ''}`}\n >\n {isPending\n ? (labels?.unnamedTopic ?? 'New topic')\n : topic.name}\n </span>\n {starter && (\n <span className='cratis-chat-topics__started-by'>\n {(\n labels?.startedBy ?? 'Started by {name}'\n ).replace('{name}', starter.name)}\n </span>\n )}\n </span>\n {activity !== undefined && (\n <span className='cratis-chat-topics__activity'>\n {relativeTimestamp(\n activity,\n new Date(),\n labels?.timestamps,\n )}\n </span>\n )}\n </button>\n </li>\n );\n })}\n </ul>\n </div>\n );\n};\n"],"names":["_jsxs","_jsx","isTopicUnnamed","defaultIsTopicUnnamed"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ChatTopicList.js","sources":["../../../Chat/ChatTopicList.tsx"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { useRef, type ReactNode } from 'react';\nimport { PersonAvatarCircle, type BuildAvatarUrlParams } from './Kit/Avatar';\nimport { ChatAuthorKind } from './Kit/ChatAuthorKind';\nimport type { ChatAuthor } from './ChatAuthor';\nimport { chatIdentifierString, type ChatIdentifier } from './ChatIdentifier';\nimport type { ChatTopic } from './ChatTopic';\nimport type { ChatTopicAction } from './ChatTopicAction';\nimport { ChatTopicActions } from './ChatTopicActions';\nimport { ChatStatus } from './ChatStatus';\nimport { isTopicUnnamed as defaultIsTopicUnnamed } from './isTopicUnnamed';\nimport { relativeTimestamp, type RelativeTimestampLabels } from './relativeTimestamp';\nimport { useTopicActionFocusRecovery } from './useTopicActionFocusRecovery';\nimport { topicsByActivity } from './topicsByActivity';\n\nconst PlusIcon = () => (\n <svg\n width='14'\n height='14'\n viewBox='0 0 24 24'\n fill='none'\n stroke='currentColor'\n strokeWidth='2.5'\n aria-hidden='true'\n >\n <line x1='12' y1='5' x2='12' y2='19' />\n <line x1='5' y1='12' x2='19' y2='12' />\n </svg>\n);\n\n/** Overrides for every label the {@link ChatTopicList} renders. Any field left unset falls back\n * to a literal English default. */\nexport interface ChatTopicListLabels {\n /** The new-topic button. Defaults to `'New topic'`. */\n newTopic?: string;\n\n /** Placeholder name for a topic that has not been named yet. Defaults to `'New topic'`. */\n unnamedTopic?: string;\n\n /** The started-by line under a topic's name. `{name}` is substituted. Defaults to `'Started by {name}'`. */\n startedBy?: string;\n\n /** Shown while loading without topics. Defaults to `'Loading topics…'`. */\n loading?: string;\n\n /** Shown when loading topics fails, alongside any existing topics. Defaults to `'Could not load topics.'`. */\n failed?: string;\n\n /** Shown when access is denied; replaces any existing topics. Defaults to `'You are not authorized to view these topics.'`. */\n unauthorized?: string;\n\n /** Shown when there are no topics yet. Defaults to `'No topics yet. Start the first one!'`. */\n empty?: string;\n\n /** Overrides for the relative last-activity timestamps. */\n timestamps?: RelativeTimestampLabels;\n}\n\n/**\n * Props for {@link ChatTopicList}.\n * @typeParam TTopic The host's topic type — anything extending {@link ChatTopic}.\n */\nexport interface ChatTopicListProps<TTopic extends ChatTopic = ChatTopic> {\n /**\n * The topics, in any order — the list orders them by most recent activity itself. Hand this\n * the array a live (observable) query delivers and the list re-renders as it changes.\n */\n topics: TTopic[];\n\n /** Query display state. Defaults to {@link ChatStatus.Ready}; existing topics remain visible on loading or failure, and unauthorized disables starting a topic. */\n status?: ChatStatus;\n\n /**\n * Invoked when a topic is picked from the list.\n * @param topic The topic that was picked.\n */\n onOpen: (topic: TTopic) => void;\n\n /**\n * Invoked when a new topic is asked for. Creating the topic is the host's business — the\n * component only raises the intent. Omit to leave the new-topic affordance off.\n */\n onStart?: () => void;\n\n /**\n * Resolves an author id into what it takes to render whoever started a topic. Omit to fall\n * back to rendering the id itself as the name.\n * @param authorId The identifier of the author to resolve.\n * @returns The author.\n */\n authorOf?: (authorId: ChatIdentifier) => ChatAuthor;\n\n /**\n * Renders the avatar for whoever started a topic, replacing the built-in initials circle.\n * @param authorId The identifier of the author.\n * @param author The resolved author, as {@link authorOf} answered.\n * @returns What to render.\n */\n renderAvatar?: (authorId: ChatIdentifier, author: ChatAuthor) => ReactNode;\n\n /**\n * Decides whether a topic counts as unnamed and renders the pending placeholder. Defaults to\n * \"no name, or only whitespace\" — override when the host's backend stores a literal\n * placeholder for unnamed topics instead.\n * @param topic The topic to check.\n * @returns True when the topic has no usable name yet.\n */\n isTopicUnnamed?: (topic: TTopic) => boolean;\n\n /** Builds the avatar image URL for topic starters. Omit to always show initials. */\n buildAvatarUrl?: (params: BuildAvatarUrlParams) => string;\n\n /** Overrides for every label rendered. Unset fields fall back to literal English defaults. */\n labels?: ChatTopicListLabels;\n\n /** The host's actions to offer on each topic where they are available. */\n topicActions?: ChatTopicAction<TTopic>[];\n\n /** Additional class name for the list's root element. */\n className?: string;\n}\n\n/**\n * The topics of a chat, most recently active first — pick one to open its conversation, or start\n * a new one. A topic that has not been named yet renders a placeholder in a pending style: with\n * host-side auto-naming, the name arrives through the data once the host has produced it, and\n * the placeholder simply stops being needed.\n */\nexport const ChatTopicList = <TTopic extends ChatTopic = ChatTopic>({\n topics,\n status = ChatStatus.Ready,\n onOpen,\n onStart,\n authorOf,\n renderAvatar,\n isTopicUnnamed,\n buildAvatarUrl,\n labels,\n topicActions,\n className,\n}: ChatTopicListProps<TTopic>) => {\n const unnamed = isTopicUnnamed ?? defaultIsTopicUnnamed;\n const listRef = useRef<HTMLUListElement>(null);\n const orderedTopics = status === ChatStatus.Unauthorized ? [] : topicsByActivity(topics);\n const { onActionFocus, onActionBlur } = useTopicActionFocusRecovery(\n listRef,\n orderedTopics.map((topic) => chatIdentifierString(topic.id)),\n );\n\n const authorFor = (authorId: ChatIdentifier): ChatAuthor =>\n authorOf?.(authorId) ?? {\n name: chatIdentifierString(authorId),\n kind: ChatAuthorKind.User,\n };\n\n return (\n <div className={`cratis-chat-topics${className ? ` ${className}` : ''}`} aria-busy={status === ChatStatus.Loading && topics.length > 0 || undefined}>\n {onStart && (\n <button\n type='button'\n className='cratis-chat-topics__start'\n disabled={status === ChatStatus.Unauthorized}\n onClick={onStart}\n >\n <PlusIcon />\n <span>{labels?.newTopic ?? 'New topic'}</span>\n </button>\n )}\n {(topics.length === 0 || status === ChatStatus.Failed || status === ChatStatus.Unauthorized) && (\n <p className='cratis-chat-topics__empty'>\n {status === ChatStatus.Loading ? (\n <span key={status} role='status'>{labels?.loading ?? 'Loading topics…'}</span>\n ) : status === ChatStatus.Failed ? (\n <span key={status} role='alert'>{labels?.failed ?? 'Could not load topics.'}</span>\n ) : status === ChatStatus.Unauthorized ? (\n <span key={status} role='alert'>{labels?.unauthorized ?? 'You are not authorized to view these topics.'}</span>\n ) : (\n labels?.empty ?? 'No topics yet. Start the first one!'\n )}\n </p>\n )}\n <ul ref={listRef} className='cratis-chat-topics__list'>\n {status !== ChatStatus.Unauthorized && orderedTopics.map((topic) => {\n const starter =\n topic.startedBy === undefined\n ? undefined\n : authorFor(topic.startedBy);\n const isPending = unnamed(topic);\n const activity = topic.lastActivity ?? topic.started;\n const topicName = isPending ? (labels?.unnamedTopic ?? 'New topic') : topic.name;\n const availableActions = (topicActions ?? []).filter(\n (action) => action.isAvailable?.(topic) ?? true,\n );\n\n const topicButton = (\n <button\n type='button'\n className='cratis-chat-topics__topic'\n onClick={() => onOpen(topic)}\n >\n {topic.startedBy !== undefined &&\n starter &&\n (renderAvatar ? (\n renderAvatar(topic.startedBy, starter)\n ) : (\n <PersonAvatarCircle\n userId={chatIdentifierString(topic.startedBy)}\n name={starter.name}\n hasAvatar={starter.hasAvatar ?? false}\n size={28}\n ownerType={\n starter.kind === ChatAuthorKind.Agent\n ? 'Agents'\n : 'Users'\n }\n version={starter.avatarVersion}\n buildAvatarUrl={buildAvatarUrl}\n />\n ))}\n <span className='cratis-chat-topics__details'>\n <span\n className={`cratis-chat-topics__name${isPending ? ' cratis-chat-topics__name--pending' : ''}`}\n >\n {topicName}\n </span>\n {starter && (\n <span className='cratis-chat-topics__started-by'>\n {(\n labels?.startedBy ?? 'Started by {name}'\n ).replace('{name}', starter.name)}\n </span>\n )}\n </span>\n {activity !== undefined && (\n <span className='cratis-chat-topics__activity'>\n {relativeTimestamp(\n activity,\n new Date(),\n labels?.timestamps,\n )}\n </span>\n )}\n </button>\n );\n\n const topicKey = chatIdentifierString(topic.id);\n // Reuses the message action overlay styling; keyboard focus reveals the controls.\n return (\n <li\n key={topicKey}\n className={availableActions.length > 0 ? 'cratis-chat-topics__row' : undefined}\n >\n {topicButton}\n {availableActions.length > 0 && (\n <ChatTopicActions<TTopic>\n topic={topic}\n topicName={topicName}\n actions={availableActions}\n onActionFocus={onActionFocus(topicKey)}\n onActionBlur={onActionBlur}\n />\n )}\n </li>\n );\n })}\n </ul>\n </div>\n );\n};\n"],"names":["_jsxs","_jsx","isTopicUnnamed","defaultIsTopicUnnamed"],"mappings":"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"}
|
package/dist/esm/Chat/index.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export type { ChatSidebarProps, ChatSidebarLabels, ChatSidebarParts, } from './C
|
|
|
14
14
|
export { ChatSidebarForObservableQueries } from './ChatSidebarForObservableQueries.js';
|
|
15
15
|
export type { ChatSidebarForObservableQueriesProps } from './ChatSidebarForObservableQueries.js';
|
|
16
16
|
export type { ChatTopic } from './ChatTopic.js';
|
|
17
|
+
export type { ChatTopicAction } from './ChatTopicAction.js';
|
|
17
18
|
export { ChatTopicList } from './ChatTopicList.js';
|
|
18
19
|
export type { ChatTopicListProps, ChatTopicListLabels } from './ChatTopicList.js';
|
|
19
20
|
export { isTopicUnnamed } from './isTopicUnnamed.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../Chat/index.ts"],"names":[],"mappings":"AAGA,YAAY,EAAE,UAAU,EAAE,MAAM,iBAAc,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAoB,CAAC;AACtD,YAAY,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,uBAAoB,CAAC;AACxF,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,qBAAkB,CAAC;AAC5E,YAAY,EAAE,cAAc,EAAE,MAAM,qBAAkB,CAAC;AACvD,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAe,CAAC;AACjD,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAe,CAAC;AACjD,YAAY,EAAE,iBAAiB,EAAE,MAAM,wBAAqB,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAc,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAmB,CAAC;AACpD,YAAY,EAAE,oBAAoB,EAAE,MAAM,sBAAmB,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAe,CAAC;AAC5C,YAAY,EACR,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,GACnB,MAAM,kBAAe,CAAC;AACvB,OAAO,EAAE,+BAA+B,EAAE,MAAM,sCAAmC,CAAC;AACpF,YAAY,EAAE,oCAAoC,EAAE,MAAM,sCAAmC,CAAC;AAC9F,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAa,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAiB,CAAC;AAChD,YAAY,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,oBAAiB,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAkB,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAqB,CAAC;AACxD,YAAY,EAAE,uBAAuB,EAAE,MAAM,wBAAqB,CAAC;AACnE,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA0B,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAoB,CAAC;AAItD,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAsB,CAAC;AACtD,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAAwB,CAAC;AAC/D,YAAY,EAAE,oBAAoB,EAAE,MAAM,uBAAc,CAAC;AACzD,YAAY,EAAE,kBAAkB,EAAE,MAAM,uBAAoB,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,yBAAgB,CAAC;AACrF,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,yBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../Chat/index.ts"],"names":[],"mappings":"AAGA,YAAY,EAAE,UAAU,EAAE,MAAM,iBAAc,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAoB,CAAC;AACtD,YAAY,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,uBAAoB,CAAC;AACxF,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,qBAAkB,CAAC;AAC5E,YAAY,EAAE,cAAc,EAAE,MAAM,qBAAkB,CAAC;AACvD,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAe,CAAC;AACjD,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAe,CAAC;AACjD,YAAY,EAAE,iBAAiB,EAAE,MAAM,wBAAqB,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAc,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAmB,CAAC;AACpD,YAAY,EAAE,oBAAoB,EAAE,MAAM,sBAAmB,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAe,CAAC;AAC5C,YAAY,EACR,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,GACnB,MAAM,kBAAe,CAAC;AACvB,OAAO,EAAE,+BAA+B,EAAE,MAAM,sCAAmC,CAAC;AACpF,YAAY,EAAE,oCAAoC,EAAE,MAAM,sCAAmC,CAAC;AAC9F,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAa,CAAC;AAC7C,YAAY,EAAE,eAAe,EAAE,MAAM,sBAAmB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAiB,CAAC;AAChD,YAAY,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,oBAAiB,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAkB,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAqB,CAAC;AACxD,YAAY,EAAE,uBAAuB,EAAE,MAAM,wBAAqB,CAAC;AACnE,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA0B,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAoB,CAAC;AAItD,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAsB,CAAC;AACtD,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAAwB,CAAC;AAC/D,YAAY,EAAE,oBAAoB,EAAE,MAAM,uBAAc,CAAC;AACzD,YAAY,EAAE,kBAAkB,EAAE,MAAM,uBAAoB,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,yBAAgB,CAAC;AACrF,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,yBAAgB,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type FocusEvent, type RefObject } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Keeps keyboard focus in a topic list when a focused topic action disappears, because
|
|
4
|
+
* invoking it made the action unavailable or removed its topic. Without this, the unmounted
|
|
5
|
+
* button drops focus to the document body.
|
|
6
|
+
*
|
|
7
|
+
* Focus moves to the same topic's opening button, or, when the topic is gone, to the topic that
|
|
8
|
+
* took its place in the order last rendered, then to the list's New topic button. Focus that the
|
|
9
|
+
* host already moved elsewhere, such as into a rename dialog, is left alone, and an action that
|
|
10
|
+
* lost focus while still mounted is forgotten.
|
|
11
|
+
* @param listRef The list whose rows each hold one topic.
|
|
12
|
+
* @param orderedTopicKeys The topic keys in rendered row order.
|
|
13
|
+
* @returns Focus and blur handlers to attach to each topic action button.
|
|
14
|
+
*/
|
|
15
|
+
export declare const useTopicActionFocusRecovery: (listRef: RefObject<HTMLUListElement | null>, orderedTopicKeys: readonly string[]) => {
|
|
16
|
+
onActionFocus: (topicKey: string) => (event: FocusEvent<HTMLElement>) => void;
|
|
17
|
+
onActionBlur: (event: FocusEvent<HTMLElement>) => void;
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=useTopicActionFocusRecovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useTopicActionFocusRecovery.d.ts","sourceRoot":"","sources":["../../../Chat/useTopicActionFocusRecovery.ts"],"names":[],"mappings":"AAGA,OAAO,EAA2B,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAEjF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,2BAA2B,YAC3B,SAAS,CAAC,gBAAgB,GAAG,IAAI,CAAC,oBACzB,SAAS,MAAM,EAAE;IA0B/B,aAAa,aAAa,MAAM,aAAa,UAAU,CAAC,WAAW,CAAC;IAGpE,YAAY,UAAU,UAAU,CAAC,WAAW,CAAC;CAWpD,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { useRef, useLayoutEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
const useTopicActionFocusRecovery = (listRef, orderedTopicKeys) => {
|
|
4
|
+
const focusedActionRef = useRef(null);
|
|
5
|
+
const committedTopicKeysRef = useRef([]);
|
|
6
|
+
useLayoutEffect(() => {
|
|
7
|
+
const previousTopicKeys = committedTopicKeysRef.current;
|
|
8
|
+
committedTopicKeysRef.current = orderedTopicKeys;
|
|
9
|
+
const focused = focusedActionRef.current;
|
|
10
|
+
if (!focused || focused.element.isConnected)
|
|
11
|
+
return;
|
|
12
|
+
focusedActionRef.current = null;
|
|
13
|
+
const active = document.activeElement;
|
|
14
|
+
if (active && active !== document.body)
|
|
15
|
+
return;
|
|
16
|
+
const rows = Array.from(listRef.current?.children ?? []);
|
|
17
|
+
const sameTopic = orderedTopicKeys.indexOf(focused.topicKey);
|
|
18
|
+
const row = sameTopic >= 0
|
|
19
|
+
? rows[sameTopic]
|
|
20
|
+
: rows[Math.min(Math.max(previousTopicKeys.indexOf(focused.topicKey), 0), rows.length - 1)];
|
|
21
|
+
const target = row?.querySelector('.cratis-chat-topics__topic') ??
|
|
22
|
+
listRef.current?.parentElement?.querySelector('.cratis-chat-topics__start');
|
|
23
|
+
target?.focus();
|
|
24
|
+
});
|
|
25
|
+
return {
|
|
26
|
+
onActionFocus: (topicKey) => (event) => {
|
|
27
|
+
focusedActionRef.current = { element: event.currentTarget, topicKey };
|
|
28
|
+
},
|
|
29
|
+
onActionBlur: (event) => {
|
|
30
|
+
const element = event.currentTarget;
|
|
31
|
+
queueMicrotask(() => {
|
|
32
|
+
if (focusedActionRef.current?.element === element && element.isConnected) {
|
|
33
|
+
focusedActionRef.current = null;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export { useTopicActionFocusRecovery };
|
|
41
|
+
//# sourceMappingURL=useTopicActionFocusRecovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useTopicActionFocusRecovery.js","sources":["../../../Chat/useTopicActionFocusRecovery.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { useLayoutEffect, useRef, type FocusEvent, type RefObject } from 'react';\n\n/**\n * Keeps keyboard focus in a topic list when a focused topic action disappears, because\n * invoking it made the action unavailable or removed its topic. Without this, the unmounted\n * button drops focus to the document body.\n *\n * Focus moves to the same topic's opening button, or, when the topic is gone, to the topic that\n * took its place in the order last rendered, then to the list's New topic button. Focus that the\n * host already moved elsewhere, such as into a rename dialog, is left alone, and an action that\n * lost focus while still mounted is forgotten.\n * @param listRef The list whose rows each hold one topic.\n * @param orderedTopicKeys The topic keys in rendered row order.\n * @returns Focus and blur handlers to attach to each topic action button.\n */\nexport const useTopicActionFocusRecovery = (\n listRef: RefObject<HTMLUListElement | null>,\n orderedTopicKeys: readonly string[],\n) => {\n const focusedActionRef = useRef<{ element: HTMLElement; topicKey: string } | null>(null);\n // The row order from the last commit, so a removed topic's position reflects any re-sorting\n // that happened after its action gained focus.\n const committedTopicKeysRef = useRef<readonly string[]>([]);\n\n useLayoutEffect(() => {\n const previousTopicKeys = committedTopicKeysRef.current;\n committedTopicKeysRef.current = orderedTopicKeys;\n const focused = focusedActionRef.current;\n if (!focused || focused.element.isConnected) return;\n focusedActionRef.current = null;\n const active = document.activeElement;\n if (active && active !== document.body) return;\n const rows = Array.from(listRef.current?.children ?? []);\n const sameTopic = orderedTopicKeys.indexOf(focused.topicKey);\n const row = sameTopic >= 0\n ? rows[sameTopic]\n : rows[Math.min(Math.max(previousTopicKeys.indexOf(focused.topicKey), 0), rows.length - 1)];\n const target = row?.querySelector<HTMLElement>('.cratis-chat-topics__topic') ??\n listRef.current?.parentElement?.querySelector<HTMLElement>('.cratis-chat-topics__start');\n target?.focus();\n });\n\n return {\n onActionFocus: (topicKey: string) => (event: FocusEvent<HTMLElement>) => {\n focusedActionRef.current = { element: event.currentTarget, topicKey };\n },\n onActionBlur: (event: FocusEvent<HTMLElement>) => {\n // A button removed while focused is already disconnected here, so only a deliberate\n // focus change on a still-mounted action clears the tracking.\n const element = event.currentTarget;\n queueMicrotask(() => {\n if (focusedActionRef.current?.element === element && element.isConnected) {\n focusedActionRef.current = null;\n }\n });\n },\n };\n};\n"],"names":[],"mappings":";;MAkBa,2BAA2B,GAAG,CACvC,OAA2C,EAC3C,gBAAmC,KACnC;AACA,IAAA,MAAM,gBAAgB,GAAG,MAAM,CAAoD,IAAI,CAAC;AAGxF,IAAA,MAAM,qBAAqB,GAAG,MAAM,CAAoB,EAAE,CAAC;IAE3D,eAAe,CAAC,MAAK;AACjB,QAAA,MAAM,iBAAiB,GAAG,qBAAqB,CAAC,OAAO;AACvD,QAAA,qBAAqB,CAAC,OAAO,GAAG,gBAAgB;AAChD,QAAA,MAAM,OAAO,GAAG,gBAAgB,CAAC,OAAO;AACxC,QAAA,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,WAAW;YAAE;AAC7C,QAAA,gBAAgB,CAAC,OAAO,GAAG,IAAI;AAC/B,QAAA,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa;AACrC,QAAA,IAAI,MAAM,IAAI,MAAM,KAAK,QAAQ,CAAC,IAAI;YAAE;AACxC,QAAA,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,QAAQ,IAAI,EAAE,CAAC;QACxD,MAAM,SAAS,GAAG,gBAAgB,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC;AAC5D,QAAA,MAAM,GAAG,GAAG,SAAS,IAAI;AACrB,cAAE,IAAI,CAAC,SAAS;AAChB,cAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,iBAAiB,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AAC/F,QAAA,MAAM,MAAM,GAAG,GAAG,EAAE,aAAa,CAAc,4BAA4B,CAAC;YACxE,OAAO,CAAC,OAAO,EAAE,aAAa,EAAE,aAAa,CAAc,4BAA4B,CAAC;QAC5F,MAAM,EAAE,KAAK,EAAE;AACnB,IAAA,CAAC,CAAC;IAEF,OAAO;QACH,aAAa,EAAE,CAAC,QAAgB,KAAK,CAAC,KAA8B,KAAI;AACpE,YAAA,gBAAgB,CAAC,OAAO,GAAG,EAAE,OAAO,EAAE,KAAK,CAAC,aAAa,EAAE,QAAQ,EAAE;QACzE,CAAC;AACD,QAAA,YAAY,EAAE,CAAC,KAA8B,KAAI;AAG7C,YAAA,MAAM,OAAO,GAAG,KAAK,CAAC,aAAa;YACnC,cAAc,CAAC,MAAK;AAChB,gBAAA,IAAI,gBAAgB,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,IAAI,OAAO,CAAC,WAAW,EAAE;AACtE,oBAAA,gBAAgB,CAAC,OAAO,GAAG,IAAI;gBACnC;AACJ,YAAA,CAAC,CAAC;QACN,CAAC;KACJ;AACL;;"}
|
|
@@ -734,10 +734,26 @@
|
|
|
734
734
|
transition: background 0.12s;
|
|
735
735
|
}
|
|
736
736
|
|
|
737
|
-
.cratis-chat-topics__topic:hover
|
|
737
|
+
.cratis-chat-topics__topic:hover,
|
|
738
|
+
.cratis-chat-topics__row:hover .cratis-chat-topics__topic {
|
|
738
739
|
background: var(--cratis-surface-hover);
|
|
739
740
|
}
|
|
740
741
|
|
|
742
|
+
/* Topic actions reuse the message action overlay, centered on the row instead of hanging above it. */
|
|
743
|
+
.cratis-chat-topics__row {
|
|
744
|
+
position: relative;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
.cratis-chat-topics__row .cratis-chat-message__actions {
|
|
748
|
+
top: 50%;
|
|
749
|
+
transform: translateY(-50%);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
.cratis-chat-topics__row:hover .cratis-chat-message__actions {
|
|
753
|
+
opacity: 1;
|
|
754
|
+
pointer-events: auto;
|
|
755
|
+
}
|
|
756
|
+
|
|
741
757
|
.cratis-chat-topics__details {
|
|
742
758
|
flex: 1;
|
|
743
759
|
min-width: 0;
|
package/dist/esm/styles.Chat.css
CHANGED
|
@@ -387,10 +387,26 @@
|
|
|
387
387
|
transition: background 0.12s;
|
|
388
388
|
}
|
|
389
389
|
|
|
390
|
-
.cratis-chat-topics__topic:hover
|
|
390
|
+
.cratis-chat-topics__topic:hover,
|
|
391
|
+
.cratis-chat-topics__row:hover .cratis-chat-topics__topic {
|
|
391
392
|
background: var(--cratis-surface-hover);
|
|
392
393
|
}
|
|
393
394
|
|
|
395
|
+
/* Topic actions reuse the message action overlay, centered on the row instead of hanging above it. */
|
|
396
|
+
.cratis-chat-topics__row {
|
|
397
|
+
position: relative;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.cratis-chat-topics__row .cratis-chat-message__actions {
|
|
401
|
+
top: 50%;
|
|
402
|
+
transform: translateY(-50%);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.cratis-chat-topics__row:hover .cratis-chat-message__actions {
|
|
406
|
+
opacity: 1;
|
|
407
|
+
pointer-events: auto;
|
|
408
|
+
}
|
|
409
|
+
|
|
394
410
|
.cratis-chat-topics__details {
|
|
395
411
|
flex: 1;
|
|
396
412
|
min-width: 0;
|
package/dist/esm/styles.css
CHANGED
|
@@ -1344,10 +1344,26 @@
|
|
|
1344
1344
|
transition: background 0.12s;
|
|
1345
1345
|
}
|
|
1346
1346
|
|
|
1347
|
-
.cratis-chat-topics__topic:hover
|
|
1347
|
+
.cratis-chat-topics__topic:hover,
|
|
1348
|
+
.cratis-chat-topics__row:hover .cratis-chat-topics__topic {
|
|
1348
1349
|
background: var(--cratis-surface-hover);
|
|
1349
1350
|
}
|
|
1350
1351
|
|
|
1352
|
+
/* Topic actions reuse the message action overlay, centered on the row instead of hanging above it. */
|
|
1353
|
+
.cratis-chat-topics__row {
|
|
1354
|
+
position: relative;
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
.cratis-chat-topics__row .cratis-chat-message__actions {
|
|
1358
|
+
top: 50%;
|
|
1359
|
+
transform: translateY(-50%);
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
.cratis-chat-topics__row:hover .cratis-chat-message__actions {
|
|
1363
|
+
opacity: 1;
|
|
1364
|
+
pointer-events: auto;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1351
1367
|
.cratis-chat-topics__details {
|
|
1352
1368
|
flex: 1;
|
|
1353
1369
|
min-width: 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cratis/components",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.20.0",
|
|
4
4
|
"description": "React components for CQRS and event-sourced apps — command dialogs, typed forms, and query-backed data tables for Cratis Arc",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -311,6 +311,9 @@
|
|
|
311
311
|
"peerDependenciesMeta": {
|
|
312
312
|
"pixi.js": {
|
|
313
313
|
"optional": true
|
|
314
|
+
},
|
|
315
|
+
"tsyringe": {
|
|
316
|
+
"optional": true
|
|
314
317
|
}
|
|
315
318
|
}
|
|
316
319
|
}
|