@dxos/react-ui-thread 0.8.4-main.fd6878d → 0.8.4-staging.60fe92afc8

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.
Files changed (50) hide show
  1. package/LICENSE +102 -5
  2. package/dist/lib/browser/chunk-QFRRLZVL.mjs +31 -0
  3. package/dist/lib/browser/chunk-QFRRLZVL.mjs.map +7 -0
  4. package/dist/lib/browser/index.mjs +508 -221
  5. package/dist/lib/browser/index.mjs.map +4 -4
  6. package/dist/lib/browser/meta.json +1 -1
  7. package/dist/lib/browser/translations.mjs +9 -0
  8. package/dist/lib/browser/translations.mjs.map +7 -0
  9. package/dist/types/src/Message/Message.d.ts +81 -10
  10. package/dist/types/src/Message/Message.d.ts.map +1 -1
  11. package/dist/types/src/Message/Message.stories.d.ts +27 -17
  12. package/dist/types/src/Message/Message.stories.d.ts.map +1 -1
  13. package/dist/types/src/Message/index.d.ts +2 -1
  14. package/dist/types/src/Message/index.d.ts.map +1 -1
  15. package/dist/types/src/Thread/Thread.d.ts +76 -13
  16. package/dist/types/src/Thread/Thread.d.ts.map +1 -1
  17. package/dist/types/src/Thread/Thread.stories.d.ts +33 -5
  18. package/dist/types/src/Thread/Thread.stories.d.ts.map +1 -1
  19. package/dist/types/src/Thread/index.d.ts +2 -1
  20. package/dist/types/src/Thread/index.d.ts.map +1 -1
  21. package/dist/types/src/command.d.ts +3 -0
  22. package/dist/types/src/command.d.ts.map +1 -0
  23. package/dist/types/src/context.d.ts +227 -0
  24. package/dist/types/src/context.d.ts.map +1 -0
  25. package/dist/types/src/index.d.ts +7 -5
  26. package/dist/types/src/index.d.ts.map +1 -1
  27. package/dist/types/src/testing.d.ts +9 -14
  28. package/dist/types/src/testing.d.ts.map +1 -1
  29. package/dist/types/src/translations.d.ts +17 -12
  30. package/dist/types/src/translations.d.ts.map +1 -1
  31. package/dist/types/src/types.d.ts +36 -3
  32. package/dist/types/src/types.d.ts.map +1 -1
  33. package/dist/types/tsconfig.tsbuildinfo +1 -1
  34. package/package.json +44 -28
  35. package/src/Message/Message.stories.tsx +40 -29
  36. package/src/Message/Message.tsx +327 -69
  37. package/src/Message/index.ts +10 -1
  38. package/src/Thread/Thread.stories.tsx +28 -72
  39. package/src/Thread/Thread.tsx +296 -38
  40. package/src/Thread/index.ts +9 -1
  41. package/src/command.ts +58 -0
  42. package/src/context.ts +21 -0
  43. package/src/index.ts +28 -6
  44. package/src/testing.ts +48 -0
  45. package/src/translations.ts +16 -11
  46. package/src/types.ts +38 -5
  47. package/dist/types/src/Thread/Comments.stories.d.ts +0 -52
  48. package/dist/types/src/Thread/Comments.stories.d.ts.map +0 -1
  49. package/src/Thread/Comments.stories.tsx +0 -371
  50. package/src/testing.tsx +0 -57
@@ -1,191 +1,201 @@
1
- // src/translations.ts
2
- var translationKey = "react-ui-thread";
3
- var translations = [
4
- {
5
- "en-US": {
6
- [translationKey]: {
7
- "plugin name": "Threads",
8
- "thread title placeholder": "New thread",
9
- "thread title label": "Title",
10
- "delete thread label": "Delete",
11
- "create thread label": "Create thread",
12
- "message placeholder": "Enter message...",
13
- "comment placeholder": "Enter comment...",
14
- "settings standalone label": "Enable standalone thread creation",
15
- "anonymous label": "Anonymous",
16
- "enter to send message": "Enter to add \u23CE"
17
- }
18
- }
19
- }
20
- ];
1
+ import {
2
+ translationKey
3
+ } from "./chunk-QFRRLZVL.mjs";
21
4
 
22
5
  // src/Thread/Thread.tsx
23
- import { useSignals as _useSignals } from "@preact-signals/safe-react/tracking";
24
- import React, { forwardRef } from "react";
25
- import { Icon, useTranslation } from "@dxos/react-ui";
26
- import { hoverableControlItem, hoverableFocusedWithinControls, mx } from "@dxos/react-ui-theme";
27
- var threadLayout = "is-full place-self-start grid grid-cols-[var(--rail-size)_1fr]";
28
- var ThreadRoot = /* @__PURE__ */ forwardRef(({ current, children, classNames, ...props }, forwardedRef) => {
29
- var _effect = _useSignals();
30
- try {
31
- return /* @__PURE__ */ React.createElement("div", {
32
- role: "group",
33
- "data-testid": "thread",
34
- ...current && {
35
- "aria-current": typeof current === "string" ? current : "location"
36
- },
37
- ...props,
38
- className: mx(threadLayout, hoverableFocusedWithinControls, "bg-[var(--surface-bg)] current-related attention-surface [--controls-opacity:0]", classNames),
39
- ref: forwardedRef
40
- }, children);
41
- } finally {
42
- _effect.f();
6
+ import React2, { forwardRef as forwardRef2, useCallback as useCallback2, useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState2 } from "react";
7
+ import { Obj as Obj2 } from "@dxos/echo";
8
+ import { Icon, IconButton as IconButton2, ScrollArea, composable, composableProps, useThemeContext as useThemeContext2, useTranslation as useTranslation2 } from "@dxos/react-ui";
9
+ import { Mosaic } from "@dxos/react-ui-mosaic";
10
+ import { createBasicExtensions as createBasicExtensions2, createThemeExtensions as createThemeExtensions2, listener as listener2 } from "@dxos/ui-editor";
11
+ import { hoverableControlItem as hoverableControlItem2, hoverableControls as hoverableControls2, hoverableFocusedWithinControls as hoverableFocusedWithinControls2, mx as mx3 } from "@dxos/ui-theme";
12
+
13
+ // src/command.ts
14
+ import { HighlightStyle, StreamLanguage, syntaxHighlighting } from "@codemirror/language";
15
+ import { EditorView } from "@codemirror/view";
16
+ import { tags } from "@dxos/ui-editor";
17
+ import { mx } from "@dxos/ui-theme";
18
+ var parser = StreamLanguage.define({
19
+ startState: () => ({
20
+ count: 0
21
+ }),
22
+ token: (stream, state) => {
23
+ state.count++;
24
+ if (stream.eatSpace()) {
25
+ return null;
26
+ }
27
+ if (state.count === 1 && stream.match(/^\/\w+/)) {
28
+ return "tagName";
29
+ }
30
+ if (stream.match(/^@\w+/)) {
31
+ return "labelName";
32
+ }
33
+ stream.next();
34
+ return null;
43
35
  }
44
36
  });
45
- var ThreadHeader = /* @__PURE__ */ forwardRef(({ classNames, children, detached, ...props }, forwardedRef) => {
46
- var _effect = _useSignals();
47
- try {
48
- return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", {
49
- role: "none",
50
- className: "flex items-center justify-center text-description"
51
- }, /* @__PURE__ */ React.createElement(Icon, {
52
- icon: "ph--caret-double-right--regular"
53
- })), /* @__PURE__ */ React.createElement("div", {
54
- role: "none",
55
- className: "flex items-center overflow-hidden"
56
- }, /* @__PURE__ */ React.createElement("p", {
57
- role: "heading",
58
- "data-testid": "thread.heading",
59
- ...props,
60
- className: mx("mie-2 text-description font-medium truncate italic", detached && "line-through decoration-1"),
61
- ref: forwardedRef
62
- }, children)));
63
- } finally {
64
- _effect.f();
37
+ var styles = HighlightStyle.define([
38
+ {
39
+ tag: tags.tagName,
40
+ class: mx("dx-tag dx-tag--indigo")
41
+ },
42
+ {
43
+ tag: tags.labelName,
44
+ class: mx("dx-tag dx-tag--blue")
65
45
  }
66
- });
67
- var ThreadStatus = /* @__PURE__ */ forwardRef(({ activity, classNames, children, ...props }, forwardedRef) => {
68
- var _effect = _useSignals();
69
- try {
70
- const { t } = useTranslation(translationKey);
71
- return /* @__PURE__ */ React.createElement("div", {
72
- ...props,
73
- className: mx("col-start-2 grid grid-cols-[min-content_1fr_max-content] pb-2 pie-2 text-xs text-description", classNames),
74
- ref: forwardedRef
75
- }, /* @__PURE__ */ React.createElement(Icon, {
76
- icon: "ph--spinner--bold",
77
- classNames: "is-6 bs-4 invisible data-[visible=show]:visible animate-spin-slow",
78
- "data-visible": activity ? "show" : "hide"
79
- }), /* @__PURE__ */ React.createElement("span", {
80
- className: "truncate min-is-0",
81
- "aria-live": "polite"
82
- }, activity ? children : null), /* @__PURE__ */ React.createElement("span", {
83
- className: mx("text-end", hoverableControlItem)
84
- }, t("enter to send message")));
85
- } finally {
86
- _effect.f();
46
+ ]);
47
+ var theme = EditorView.theme({
48
+ ".dx-tag": {
49
+ verticalAlign: "bottom"
87
50
  }
88
51
  });
89
- var Thread = {
90
- Root: ThreadRoot,
91
- Header: ThreadHeader,
92
- Status: ThreadStatus
93
- };
52
+ var command = [
53
+ parser,
54
+ syntaxHighlighting(styles),
55
+ theme
56
+ ];
57
+
58
+ // src/context.ts
59
+ import { createContext } from "@radix-ui/react-context";
60
+ var [ThreadContextProvider, useThreadContext] = createContext("Thread");
94
61
 
95
62
  // src/Message/Message.tsx
96
- import { useSignals as _useSignals2 } from "@preact-signals/safe-react/tracking";
63
+ import { EditorView as EditorView2 } from "@codemirror/view";
97
64
  import { formatDistanceToNow } from "date-fns/formatDistanceToNow";
98
- import React2, { forwardRef as forwardRef2 } from "react";
99
- import { Avatar, useTranslation as useTranslation2 } from "@dxos/react-ui";
100
- import { keymap, listener, useTextEditor } from "@dxos/react-ui-editor";
101
- import { focusRing, mx as mx2 } from "@dxos/react-ui-theme";
102
- import { hexToEmoji, hexToHue, isNotFalsy } from "@dxos/util";
65
+ import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from "react";
66
+ import { Obj, Ref } from "@dxos/echo";
67
+ import { Avatar, IconButton, useOnTransition, useThemeContext, useTranslation } from "@dxos/react-ui";
68
+ import { useTextEditor } from "@dxos/react-ui-editor";
69
+ import { createBasicExtensions, createThemeExtensions, keymap, listener } from "@dxos/ui-editor";
70
+ import { hoverableControlItem, hoverableControls, hoverableFocusedWithinControls, mx as mx2 } from "@dxos/ui-theme";
71
+ import { hexToEmoji, hexToHue, isTruthy } from "@dxos/util";
103
72
  var avatarSize = 7;
104
- var MessageRoot = /* @__PURE__ */ forwardRef2(({ authorImgSrc, authorId, authorName, authorAvatarProps, continues = true, children, classNames, ...rootProps }, forwardedRef) => {
105
- var _effect = _useSignals2();
106
- try {
107
- return (
108
- // Must wrap the message since Avatar.Label may be used in the content.
109
- /* @__PURE__ */ React2.createElement(Avatar.Root, null, /* @__PURE__ */ React2.createElement("div", {
110
- role: "none",
111
- "data-testid": "thread.message",
112
- ...rootProps,
113
- className: mx2("grid grid-cols-subgrid col-span-2", classNames),
114
- ref: forwardedRef
115
- }, /* @__PURE__ */ React2.createElement("div", {
116
- role: "none",
117
- className: "flex flex-col items-center gap-2 pbs-2"
118
- }, /* @__PURE__ */ React2.createElement(Avatar.Content, {
119
- size: avatarSize,
120
- hue: authorAvatarProps?.hue || hexToHue(authorId ?? "0"),
121
- fallback: authorAvatarProps?.emoji || hexToEmoji(authorId ?? "0"),
122
- ...authorImgSrc && {
123
- imgSrc: authorImgSrc
124
- }
125
- }), continues && /* @__PURE__ */ React2.createElement("div", {
126
- role: "none",
127
- className: "is-px grow bg-separator"
128
- })), /* @__PURE__ */ React2.createElement("div", {
129
- role: "none",
130
- className: "plb-1 min-is-0"
131
- }, children)))
132
- );
133
- } finally {
134
- _effect.f();
135
- }
73
+ var buttonGroupClassNames = "flex flex-row items-center gap-0.5 pe-2";
74
+ var buttonClassNames = "p-1! transition-opacity";
75
+ var MessageRoot = /* @__PURE__ */ forwardRef(({ authorImgSrc, authorId, authorName, authorAvatarProps, continues = true, controls, children, classNames, ...rootProps }, forwardedRef) => {
76
+ return /* @__PURE__ */ React.createElement(Avatar.Root, null, /* @__PURE__ */ React.createElement("div", {
77
+ "data-testid": "thread.message",
78
+ ...rootProps,
79
+ className: mx2("grid grid-cols-[var(--dx-rail-size)_1fr_min-content] w-full", classNames),
80
+ ref: forwardedRef
81
+ }, /* @__PURE__ */ React.createElement("div", {
82
+ className: "flex flex-col items-center gap-2 pt-1"
83
+ }, /* @__PURE__ */ React.createElement(Avatar.Content, {
84
+ size: avatarSize,
85
+ hue: authorAvatarProps?.hue || hexToHue(authorId ?? "0"),
86
+ fallback: authorAvatarProps?.emoji || hexToEmoji(authorId ?? "0"),
87
+ ...authorImgSrc && {
88
+ imgSrc: authorImgSrc
89
+ }
90
+ }), continues && /* @__PURE__ */ React.createElement("div", {
91
+ className: "w-px grow bg-separator"
92
+ })), /* @__PURE__ */ React.createElement("div", {
93
+ className: "py-1 min-w-0"
94
+ }, children), controls && /* @__PURE__ */ React.createElement("div", {
95
+ className: "self-start"
96
+ }, controls)));
136
97
  });
98
+ MessageRoot.displayName = "Message.Root";
137
99
  var MessageHeading = ({ children, classNames, timestamp, authorName, ...props }) => {
138
- var _effect = _useSignals2();
139
- try {
140
- return /* @__PURE__ */ React2.createElement("div", {
141
- role: "none",
142
- ...props,
143
- className: mx2("flex gap-2 items-start", classNames)
144
- }, /* @__PURE__ */ React2.createElement("p", {
145
- className: "grow"
146
- }, /* @__PURE__ */ React2.createElement(MessageAuthorName, {
147
- authorName
148
- }), timestamp && /* @__PURE__ */ React2.createElement(MessageTime, {
149
- timestamp
150
- })), children);
151
- } finally {
152
- _effect.f();
153
- }
100
+ return /* @__PURE__ */ React.createElement("div", {
101
+ ...props,
102
+ className: mx2("flex gap-2 items-start", classNames)
103
+ }, /* @__PURE__ */ React.createElement("p", {
104
+ className: "grow"
105
+ }, /* @__PURE__ */ React.createElement(MessageAuthorName, {
106
+ authorName
107
+ }), timestamp && /* @__PURE__ */ React.createElement(MessageTime, {
108
+ timestamp
109
+ })), children);
154
110
  };
111
+ MessageHeading.displayName = "Message.Heading";
155
112
  var MessageAuthorName = ({ authorName }) => {
156
- var _effect = _useSignals2();
157
- try {
158
- const { t } = useTranslation2(translationKey);
159
- return /* @__PURE__ */ React2.createElement(Avatar.Label, {
160
- classNames: "block truncate text-sm text-subdued"
161
- }, authorName ?? t("anonymous label"));
162
- } finally {
163
- _effect.f();
164
- }
113
+ const { t } = useTranslation(translationKey);
114
+ return /* @__PURE__ */ React.createElement(Avatar.Label, {
115
+ classNames: "block truncate text-sm text-subdued"
116
+ }, authorName ?? t("anonymous.label"));
165
117
  };
118
+ MessageAuthorName.displayName = "Message.AuthorName";
166
119
  var MessageTime = ({ timestamp }) => {
167
- var _effect = _useSignals2();
168
- try {
169
- const { dtLocale } = useTranslation2(translationKey);
170
- const dt = timestamp ? new Date(timestamp) : void 0;
171
- return /* @__PURE__ */ React2.createElement("time", {
172
- className: "block text-subdued text-xs pbe-0.5",
173
- dateTime: dt?.toISOString()
174
- }, dt ? formatDistanceToNow(dt, {
175
- locale: dtLocale,
176
- addSuffix: true
177
- }) : "");
178
- } finally {
179
- _effect.f();
180
- }
120
+ const { dtLocale } = useTranslation(translationKey);
121
+ const dt = timestamp ? new Date(timestamp) : void 0;
122
+ return /* @__PURE__ */ React.createElement("time", {
123
+ className: "block text-subdued text-xs pb-0.5",
124
+ dateTime: dt?.toISOString()
125
+ }, dt ? formatDistanceToNow(dt, {
126
+ locale: dtLocale,
127
+ addSuffix: true
128
+ }) : "");
181
129
  };
182
- var MessageBody = ({ children, ...props }) => {
183
- var _effect = _useSignals2();
184
- try {
185
- return /* @__PURE__ */ React2.createElement("p", props, children);
186
- } finally {
187
- _effect.f();
188
- }
130
+ MessageTime.displayName = "Message.Time";
131
+ var MessageBody = ({ message, isAuthor, editing, onSave }) => {
132
+ const { components } = useThreadContext("Message.Body");
133
+ const textBlockIndex = message.blocks.findIndex((block) => block._tag === "text");
134
+ const textBlock = textBlockIndex !== -1 ? message.blocks[textBlockIndex] : void 0;
135
+ const proposalBlock = message.blocks.find((block) => block._tag === "proposal");
136
+ const references = message.blocks.filter((block) => block._tag === "reference").map((block) => block.reference);
137
+ const Object = components.Object;
138
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, textBlock && /* @__PURE__ */ React.createElement(TextBlock, {
139
+ block: textBlock,
140
+ isAuthor,
141
+ editing,
142
+ onSave
143
+ }), proposalBlock && /* @__PURE__ */ React.createElement("div", {
144
+ className: "me-4 italic"
145
+ }, proposalBlock.text), Object && Ref.Array.targets(references).map((reference, index) => /* @__PURE__ */ React.createElement(Object, {
146
+ key: index,
147
+ subject: reference
148
+ })));
149
+ };
150
+ MessageBody.displayName = "Message.Body";
151
+ var TextBlock = ({ block, isAuthor, editing, onSave }) => {
152
+ const { themeMode } = useThemeContext();
153
+ const inMemoryContentRef = useRef(block.text);
154
+ const handleDocumentChange = useCallback((next) => {
155
+ inMemoryContentRef.current = next;
156
+ }, []);
157
+ const saveDocumentChange = useCallback(() => {
158
+ onSave?.(inMemoryContentRef.current);
159
+ }, [
160
+ onSave
161
+ ]);
162
+ useOnTransition(editing, true, false, saveDocumentChange);
163
+ const { parentRef, focusAttributes, view } = useTextEditor(() => ({
164
+ initialValue: block.text,
165
+ extensions: [
166
+ createBasicExtensions({
167
+ readOnly: !isAuthor || !editing
168
+ }),
169
+ createThemeExtensions({
170
+ themeMode
171
+ }),
172
+ command,
173
+ EditorView2.updateListener.of((update) => {
174
+ if (update.docChanged) {
175
+ handleDocumentChange(update.state.doc.toString());
176
+ }
177
+ })
178
+ ]
179
+ }), [
180
+ block.text,
181
+ editing,
182
+ isAuthor,
183
+ themeMode,
184
+ handleDocumentChange
185
+ ]);
186
+ useEffect(() => {
187
+ if (editing) {
188
+ view?.focus();
189
+ }
190
+ }, [
191
+ editing,
192
+ view
193
+ ]);
194
+ return /* @__PURE__ */ React.createElement("div", {
195
+ ref: parentRef,
196
+ className: "me-4",
197
+ ...focusAttributes
198
+ });
189
199
  };
190
200
  var keyBindings = ({ onSend, onClear }) => [
191
201
  {
@@ -194,74 +204,351 @@ var keyBindings = ({ onSend, onClear }) => [
194
204
  if (onSend) {
195
205
  onSend();
196
206
  return true;
197
- } else {
198
- return false;
199
207
  }
208
+ return false;
200
209
  }
201
210
  },
202
211
  {
203
- // TODO(burdon): Other key bindings.
204
212
  key: "Meta+Backspace",
205
213
  run: () => {
206
214
  if (onClear) {
207
215
  onClear();
208
216
  return true;
209
- } else {
210
- return false;
211
217
  }
218
+ return false;
212
219
  }
213
220
  }
214
221
  ];
215
- var MessageTextbox = ({ id, authorId, authorName, authorImgSrc, authorAvatarProps, disabled, extensions, onSend, onClear, onEditorFocus, ...editorProps }) => {
216
- var _effect = _useSignals2();
217
- try {
218
- const { parentRef, focusAttributes } = useTextEditor(() => ({
219
- id,
220
- extensions: [
221
- keymap.of(keyBindings({
222
- onSend,
223
- onClear
224
- })),
225
- listener({
226
- onFocus: (focusing) => {
227
- if (focusing) {
228
- onEditorFocus?.();
229
- }
222
+ var MessageTextbox = /* @__PURE__ */ forwardRef(({ id, authorId, authorName, authorImgSrc, authorAvatarProps, disabled, extensions, onSend, onClear, onEditorFocus, ...editorProps }, forwardedRef) => {
223
+ const { parentRef, focusAttributes, view } = useTextEditor(() => ({
224
+ id,
225
+ extensions: [
226
+ keymap.of(keyBindings({
227
+ onSend,
228
+ onClear
229
+ })),
230
+ listener({
231
+ onFocus: ({ focusing }) => {
232
+ if (focusing) {
233
+ onEditorFocus?.();
230
234
  }
231
- }),
232
- extensions
233
- ].filter(isNotFalsy),
234
- ...editorProps
235
- }), [
236
- id,
235
+ }
236
+ }),
237
237
  extensions
238
- ]);
239
- return /* @__PURE__ */ React2.createElement(MessageRoot, {
240
- id,
241
- authorId,
242
- authorName,
243
- authorImgSrc,
244
- authorAvatarProps,
245
- continues: false
246
- }, /* @__PURE__ */ React2.createElement("div", {
247
- role: "none",
248
- ref: parentRef,
249
- className: mx2("plb-0.5 mie-1 rounded-sm", focusRing, disabled && "opacity-50"),
250
- ...focusAttributes
251
- }));
252
- } finally {
253
- _effect.f();
254
- }
238
+ ].filter(isTruthy),
239
+ ...editorProps
240
+ }), [
241
+ id,
242
+ extensions
243
+ ]);
244
+ useImperativeHandle(forwardedRef, () => ({
245
+ focus: () => view?.focus()
246
+ }), [
247
+ view
248
+ ]);
249
+ return /* @__PURE__ */ React.createElement(MessageRoot, {
250
+ id,
251
+ authorId,
252
+ authorName,
253
+ authorImgSrc,
254
+ authorAvatarProps,
255
+ continues: false
256
+ }, /* @__PURE__ */ React.createElement("div", {
257
+ ref: parentRef,
258
+ className: mx2("py-0.5 me-1 rounded-xs dx-focus-ring", disabled && "opacity-50"),
259
+ ...focusAttributes
260
+ }));
261
+ });
262
+ MessageTextbox.displayName = "Message.Textbox";
263
+ var MessageTile = ({ message, classNames }) => {
264
+ const { t } = useTranslation(translationKey);
265
+ const { getMetadata, identityDid, editable, onMessageDelete, onAcceptProposal } = useThreadContext("Message.Tile");
266
+ const [editing, setEditing] = useState(false);
267
+ const metadata = getMetadata(message);
268
+ const isAuthor = !!identityDid && identityDid === metadata.authorId;
269
+ const hasProposal = message.blocks.some((block) => block._tag === "proposal");
270
+ const handleEdit = useCallback(() => setEditing((value) => !value), []);
271
+ const handleDelete = useCallback(() => onMessageDelete?.(message.id), [
272
+ onMessageDelete,
273
+ message.id
274
+ ]);
275
+ const handleAcceptProposal = useCallback(() => onAcceptProposal?.(message.id), [
276
+ onAcceptProposal,
277
+ message.id
278
+ ]);
279
+ const handleSave = useCallback((text) => {
280
+ Obj.update(message, (message2) => {
281
+ const block = message2.blocks.find((block2) => block2._tag === "text");
282
+ if (block && block._tag === "text") {
283
+ block.text = text;
284
+ }
285
+ });
286
+ }, [
287
+ message
288
+ ]);
289
+ const showEdit = isAuthor && editable;
290
+ const showAccept = hasProposal && !!onAcceptProposal;
291
+ const showDelete = !!onMessageDelete;
292
+ const controls = showEdit || showAccept || showDelete ? /* @__PURE__ */ React.createElement("div", {
293
+ className: buttonGroupClassNames
294
+ }, showEdit && /* @__PURE__ */ React.createElement(IconButton, {
295
+ "data-testid": editing ? "thread.message.save" : "thread.message.edit",
296
+ variant: "ghost",
297
+ icon: editing ? "ph--check--regular" : "ph--pencil-simple--regular",
298
+ iconOnly: true,
299
+ label: t(editing ? "save-message.label" : "edit-message.label"),
300
+ classNames: [
301
+ buttonClassNames,
302
+ hoverableControlItem
303
+ ],
304
+ onClick: handleEdit
305
+ }), showAccept && /* @__PURE__ */ React.createElement(IconButton, {
306
+ "data-testid": "thread.message.accept",
307
+ variant: "ghost",
308
+ icon: "ph--check--regular",
309
+ iconOnly: true,
310
+ label: t("accept-proposal.label"),
311
+ classNames: [
312
+ buttonClassNames,
313
+ hoverableControlItem
314
+ ],
315
+ onClick: handleAcceptProposal
316
+ }), showDelete && /* @__PURE__ */ React.createElement(IconButton, {
317
+ "data-testid": "thread.message.delete",
318
+ variant: "ghost",
319
+ icon: "ph--x--regular",
320
+ iconOnly: true,
321
+ label: t("delete-message.label"),
322
+ classNames: [
323
+ buttonClassNames,
324
+ hoverableControlItem
325
+ ],
326
+ onClick: handleDelete
327
+ })) : void 0;
328
+ return /* @__PURE__ */ React.createElement(MessageRoot, {
329
+ ...metadata,
330
+ controls,
331
+ classNames: [
332
+ hoverableControls,
333
+ hoverableFocusedWithinControls,
334
+ classNames
335
+ ]
336
+ }, /* @__PURE__ */ React.createElement(MessageHeading, {
337
+ authorName: metadata.authorName,
338
+ timestamp: metadata.timestamp
339
+ }), /* @__PURE__ */ React.createElement(MessageBody, {
340
+ message,
341
+ isAuthor,
342
+ editing,
343
+ onSave: handleSave
344
+ }));
345
+ };
346
+ MessageTile.displayName = "Message.Tile";
347
+ var Message = {
348
+ Root: MessageRoot,
349
+ Heading: MessageHeading,
350
+ AuthorName: MessageAuthorName,
351
+ Time: MessageTime,
352
+ Body: MessageBody,
353
+ Textbox: MessageTextbox,
354
+ Tile: MessageTile
355
+ };
356
+
357
+ // src/Thread/Thread.tsx
358
+ var getMessageId = (message) => Obj2.getURI(message);
359
+ var ThreadRoot = ({ children, getMetadata, components, identityDid, editable, onMessageDelete, onAcceptProposal }) => {
360
+ const composerFocus = useRef2(void 0);
361
+ const registerComposerFocus = useCallback2((focus) => {
362
+ composerFocus.current = focus;
363
+ }, []);
364
+ const focusComposer = useCallback2(() => composerFocus.current?.(), []);
365
+ return /* @__PURE__ */ React2.createElement(ThreadContextProvider, {
366
+ getMetadata,
367
+ components: components ?? {},
368
+ identityDid,
369
+ editable,
370
+ registerComposerFocus,
371
+ focusComposer,
372
+ onMessageDelete,
373
+ onAcceptProposal
374
+ }, /* @__PURE__ */ React2.createElement(Mosaic.Root, null, children));
375
+ };
376
+ ThreadRoot.displayName = "Thread.Root";
377
+ var ThreadContent = composable(({ children, current, id, ...props }, forwardedRef) => {
378
+ return /* @__PURE__ */ React2.createElement("div", {
379
+ ...composableProps(props, {
380
+ role: "group",
381
+ classNames: [
382
+ "flex flex-col bg-[var(--surface-bg)] current-related dx-attention-surface [--controls-opacity:0]",
383
+ hoverableFocusedWithinControls2
384
+ ]
385
+ }),
386
+ "data-testid": "thread",
387
+ id,
388
+ "aria-current": current ? "location" : void 0,
389
+ ref: forwardedRef
390
+ }, children);
391
+ });
392
+ ThreadContent.displayName = "Thread.Content";
393
+ var ThreadHeader = composable(({ title, detached, current, controls, onSelect, ...props }, forwardedRef) => {
394
+ const { t } = useTranslation2(translationKey);
395
+ const { focusComposer } = useThreadContext("Thread.Header");
396
+ const handleSelect = useCallback2(() => {
397
+ onSelect?.();
398
+ focusComposer();
399
+ }, [
400
+ onSelect,
401
+ focusComposer
402
+ ]);
403
+ return /* @__PURE__ */ React2.createElement("div", {
404
+ ...composableProps(props, {
405
+ classNames: [
406
+ "grid grid-cols-[var(--dx-rail-size)_1fr_min-content] items-center",
407
+ hoverableControls2,
408
+ hoverableFocusedWithinControls2
409
+ ]
410
+ }),
411
+ ref: forwardedRef
412
+ }, /* @__PURE__ */ React2.createElement("div", {
413
+ className: "flex items-center justify-center"
414
+ }, /* @__PURE__ */ React2.createElement(IconButton2, {
415
+ iconOnly: true,
416
+ variant: "ghost",
417
+ density: "sm",
418
+ icon: "ph--caret-double-right--regular",
419
+ label: t("select-thread.label"),
420
+ classNames: "text-description",
421
+ onClick: handleSelect
422
+ })), /* @__PURE__ */ React2.createElement("div", {
423
+ className: "flex items-center overflow-hidden"
424
+ }, /* @__PURE__ */ React2.createElement("p", {
425
+ role: "heading",
426
+ "data-testid": "thread.heading",
427
+ className: mx3("me-2 font-medium truncate italic", current ? "text-accent-text" : "text-description", detached && "line-through decoration-1")
428
+ }, title)), controls);
429
+ });
430
+ ThreadHeader.displayName = "Thread.Header";
431
+ var MessageTileAdapter = ({ id, data, location, draggable, current, selected }) => /* @__PURE__ */ React2.createElement(Mosaic.Tile, {
432
+ id,
433
+ data,
434
+ location,
435
+ draggable,
436
+ current,
437
+ selected
438
+ }, /* @__PURE__ */ React2.createElement(Message.Tile, {
439
+ message: data
440
+ }));
441
+ var ThreadMessages = ({ messages, estimateSize = 80, currentId, classNames }) => {
442
+ const [viewport, setViewport] = useState2(null);
443
+ const items = useMemo(() => messages.filter(Boolean), [
444
+ messages
445
+ ]);
446
+ return /* @__PURE__ */ React2.createElement(Mosaic.Container, {
447
+ asChild: true,
448
+ orientation: "vertical",
449
+ autoScroll: viewport,
450
+ currentId,
451
+ eventHandler: {
452
+ id: "thread",
453
+ canDrop: () => false
454
+ }
455
+ }, /* @__PURE__ */ React2.createElement(ScrollArea.Root, {
456
+ classNames: mx3("col-span-2", classNames),
457
+ orientation: "vertical"
458
+ }, /* @__PURE__ */ React2.createElement(ScrollArea.Viewport, {
459
+ ref: setViewport
460
+ }, /* @__PURE__ */ React2.createElement(Mosaic.VirtualStack, {
461
+ items,
462
+ getId: getMessageId,
463
+ Tile: MessageTileAdapter,
464
+ draggable: false,
465
+ getScrollElement: () => viewport,
466
+ estimateSize: () => estimateSize
467
+ }))));
468
+ };
469
+ ThreadMessages.displayName = "Thread.Messages";
470
+ var ThreadTextbox = ({ placeholder, autoFocus, disabled, extensions, onSend, ...metadata }) => {
471
+ const { t } = useTranslation2(translationKey);
472
+ const { themeMode } = useThemeContext2();
473
+ const { registerComposerFocus } = useThreadContext("Thread.Textbox");
474
+ const composerRef = useRef2(null);
475
+ const messageRef = useRef2("");
476
+ const [count, setCount] = useState2(0);
477
+ useEffect2(() => {
478
+ registerComposerFocus(() => composerRef.current?.focus());
479
+ return () => registerComposerFocus(void 0);
480
+ }, [
481
+ registerComposerFocus
482
+ ]);
483
+ const editorExtensions = useMemo(() => [
484
+ createBasicExtensions2({
485
+ placeholder: placeholder ?? t("message.placeholder")
486
+ }),
487
+ createThemeExtensions2({
488
+ themeMode
489
+ }),
490
+ listener2({
491
+ onChange: ({ text }) => messageRef.current = text
492
+ }),
493
+ command,
494
+ extensions
495
+ ].filter(Boolean), [
496
+ themeMode,
497
+ placeholder,
498
+ t,
499
+ extensions,
500
+ count
501
+ ]);
502
+ const handleSend = useCallback2(() => {
503
+ const text = messageRef.current;
504
+ if (!text?.length || !onSend) {
505
+ return;
506
+ }
507
+ if (onSend(text)) {
508
+ messageRef.current = "";
509
+ setCount((value) => value + 1);
510
+ }
511
+ }, []);
512
+ return /* @__PURE__ */ React2.createElement(Message.Textbox, {
513
+ ref: composerRef,
514
+ ...metadata,
515
+ autoFocus,
516
+ disabled,
517
+ extensions: editorExtensions,
518
+ onSend: handleSend
519
+ });
520
+ };
521
+ ThreadTextbox.displayName = "Thread.Textbox";
522
+ var ThreadStatus = /* @__PURE__ */ forwardRef2(({ activity, classNames, children, ...props }, forwardedRef) => {
523
+ const { t } = useTranslation2(translationKey);
524
+ return /* @__PURE__ */ React2.createElement("div", {
525
+ ...props,
526
+ className: mx3("col-start-2 grid grid-cols-[min-content_1fr_max-content] pb-2 pe-2 text-xs text-description", classNames),
527
+ ref: forwardedRef
528
+ }, /* @__PURE__ */ React2.createElement(Icon, {
529
+ icon: "ph--spinner--bold",
530
+ classNames: "w-6 h-4 invisible data-[visible=show]:visible animate-spin-slow",
531
+ "data-visible": activity ? "show" : "hide"
532
+ }), /* @__PURE__ */ React2.createElement("span", {
533
+ className: "truncate min-w-0",
534
+ "aria-live": "polite"
535
+ }, activity ? children : null), /* @__PURE__ */ React2.createElement("span", {
536
+ className: mx3("text-end", hoverableControlItem2)
537
+ }, t("enter-to-send.message")));
538
+ });
539
+ ThreadStatus.displayName = "Thread.Status";
540
+ var Thread = {
541
+ Root: ThreadRoot,
542
+ Content: ThreadContent,
543
+ Header: ThreadHeader,
544
+ Messages: ThreadMessages,
545
+ Textbox: ThreadTextbox,
546
+ Status: ThreadStatus
255
547
  };
256
548
  export {
257
- MessageAuthorName,
258
- MessageBody,
259
- MessageHeading,
260
- MessageRoot,
261
- MessageTextbox,
262
- MessageTime,
549
+ Message,
263
550
  Thread,
264
- threadLayout,
265
- translations
551
+ command,
552
+ useThreadContext
266
553
  };
267
554
  //# sourceMappingURL=index.mjs.map