@dxos/react-ui-chat 0.8.4-main.5ea62a8 → 0.8.4-main.60689f5b1c

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 (55) hide show
  1. package/LICENSE +102 -5
  2. package/dist/lib/browser/index.mjs +229 -390
  3. package/dist/lib/browser/index.mjs.map +4 -4
  4. package/dist/lib/browser/meta.json +1 -1
  5. package/dist/lib/browser/translations.mjs +14 -0
  6. package/dist/lib/browser/translations.mjs.map +7 -0
  7. package/dist/lib/node-esm/index.mjs +229 -390
  8. package/dist/lib/node-esm/index.mjs.map +4 -4
  9. package/dist/lib/node-esm/meta.json +1 -1
  10. package/dist/lib/node-esm/translations.mjs +16 -0
  11. package/dist/lib/node-esm/translations.mjs.map +7 -0
  12. package/dist/types/src/components/ChatDialog/ChatDialog.d.ts +17 -1
  13. package/dist/types/src/components/ChatDialog/ChatDialog.d.ts.map +1 -1
  14. package/dist/types/src/components/ChatDialog/ChatDialog.stories.d.ts +2 -3
  15. package/dist/types/src/components/ChatDialog/ChatDialog.stories.d.ts.map +1 -1
  16. package/dist/types/src/components/ChatEditor/ChatEditor.d.ts +7 -9
  17. package/dist/types/src/components/ChatEditor/ChatEditor.d.ts.map +1 -1
  18. package/dist/types/src/components/ChatEditor/ChatEditor.stories.d.ts +3 -4
  19. package/dist/types/src/components/ChatEditor/ChatEditor.stories.d.ts.map +1 -1
  20. package/dist/types/src/components/ChatEditor/references.d.ts +4 -0
  21. package/dist/types/src/components/ChatEditor/references.d.ts.map +1 -1
  22. package/dist/types/src/components/ChatStatus/ChatStatus.d.ts +40 -0
  23. package/dist/types/src/components/ChatStatus/ChatStatus.d.ts.map +1 -0
  24. package/dist/types/src/components/ChatStatus/ChatStatus.stories.d.ts +27 -0
  25. package/dist/types/src/components/ChatStatus/ChatStatus.stories.d.ts.map +1 -0
  26. package/dist/types/src/components/ChatStatus/format.d.ts +7 -0
  27. package/dist/types/src/components/ChatStatus/format.d.ts.map +1 -0
  28. package/dist/types/src/components/ChatStatus/format.test.d.ts +2 -0
  29. package/dist/types/src/components/ChatStatus/format.test.d.ts.map +1 -0
  30. package/dist/types/src/components/ChatStatus/index.d.ts +3 -0
  31. package/dist/types/src/components/ChatStatus/index.d.ts.map +1 -0
  32. package/dist/types/src/components/index.d.ts +1 -0
  33. package/dist/types/src/components/index.d.ts.map +1 -1
  34. package/dist/types/src/index.d.ts +0 -1
  35. package/dist/types/src/index.d.ts.map +1 -1
  36. package/dist/types/src/translations.d.ts +2 -2
  37. package/dist/types/src/translations.d.ts.map +1 -1
  38. package/dist/types/tsconfig.tsbuildinfo +1 -1
  39. package/package.json +52 -43
  40. package/src/components/ChatDialog/ChatDialog.stories.tsx +4 -6
  41. package/src/components/ChatDialog/ChatDialog.tsx +28 -18
  42. package/src/components/ChatEditor/ChatEditor.stories.tsx +20 -20
  43. package/src/components/ChatEditor/ChatEditor.tsx +34 -63
  44. package/src/components/ChatEditor/references.ts +18 -12
  45. package/src/components/ChatStatus/ChatStatus.stories.tsx +130 -0
  46. package/src/components/ChatStatus/ChatStatus.tsx +183 -0
  47. package/src/components/ChatStatus/format.test.ts +48 -0
  48. package/src/components/ChatStatus/format.ts +27 -0
  49. package/src/components/ChatStatus/index.ts +6 -0
  50. package/src/components/index.ts +1 -0
  51. package/src/index.ts +0 -1
  52. package/src/translations.ts +1 -1
  53. package/dist/types/src/components/ChatEditor/autocomplete.d.ts +0 -26
  54. package/dist/types/src/components/ChatEditor/autocomplete.d.ts.map +0 -1
  55. package/src/components/ChatEditor/autocomplete.ts +0 -220
@@ -1,121 +1,97 @@
1
1
  // src/components/ChatDialog/ChatDialog.tsx
2
- import { useSignals as _useSignals } from "@preact-signals/safe-react/tracking";
3
2
  import { createContext } from "@radix-ui/react-context";
4
3
  import React, { useEffect, useState } from "react";
5
4
  import { Dialog, Icon, IconButton, useControlledState } from "@dxos/react-ui";
6
5
  import { ResizeHandle, resizeAttributes, sizeStyle } from "@dxos/react-ui-dnd";
7
- import { mx } from "@dxos/react-ui-theme";
6
+ import { mx } from "@dxos/ui-theme";
8
7
  var preventDefault = (event) => event.preventDefault();
9
8
  var Endcap = ({ children }) => {
10
- var _effect = _useSignals();
11
- try {
12
- return /* @__PURE__ */ React.createElement("div", {
13
- className: "grid w-[var(--rail-action)] h-[var(--rail-action)] items-center justify-center"
14
- }, children);
15
- } finally {
16
- _effect.f();
17
- }
9
+ return /* @__PURE__ */ React.createElement("div", {
10
+ className: "grid w-[var(--dx-rail-action)] h-[var(--dx-rail-action)] items-center justify-center"
11
+ }, children);
18
12
  };
19
13
  var [ChatDialogContextProvider, useChatDialogContext] = createContext("ChatDialog");
20
- var ChatDialogRoot = ({ children, open: _open = false, expanded: _expanded = false, onOpenChange, onExpandedChange, onEscape }) => {
21
- var _effect = _useSignals();
22
- try {
23
- const [size, setSize] = useState("min-content");
24
- const [open, setOpen] = useControlledState(_open, onOpenChange);
25
- const [expanded, setExpanded] = useControlledState(_expanded, onExpandedChange);
26
- return /* @__PURE__ */ React.createElement(ChatDialogContextProvider, {
27
- open,
28
- setOpen,
29
- expanded,
30
- setExpanded,
31
- size,
32
- setSize
33
- }, /* @__PURE__ */ React.createElement(Dialog.Root, {
34
- modal: false,
35
- open,
36
- onOpenChange: setOpen
37
- }, /* @__PURE__ */ React.createElement("div", {
38
- role: "none",
39
- className: "dx-dialog__overlay bg-transparent pointer-events-none",
40
- "data-block-align": "end"
41
- }, /* @__PURE__ */ React.createElement(Dialog.Content, {
42
- inOverlayLayout: true,
43
- classNames: [
44
- "grid grid-rows-[var(--rail-action)_1fr_min-content] p-0 overflow-hidden",
45
- "box-content md:is-[35rem] md:max-is-none pointer-events-auto"
46
- ],
47
- onEscapeKeyDown: onEscape,
48
- onInteractOutside: preventDefault
49
- }, children))));
50
- } finally {
51
- _effect.f();
52
- }
14
+ var ChatDialogRoot = ({ children, open: openProp = false, expanded: expandedProp = false, onOpenChange, onExpandedChange, onEscape }) => {
15
+ const [size, setSize] = useState("min-content");
16
+ const [open, setOpen] = useControlledState(openProp, onOpenChange);
17
+ const [expanded, setExpanded] = useControlledState(expandedProp, onExpandedChange);
18
+ return /* @__PURE__ */ React.createElement(ChatDialogContextProvider, {
19
+ open,
20
+ setOpen,
21
+ expanded,
22
+ setExpanded,
23
+ size,
24
+ setSize
25
+ }, /* @__PURE__ */ React.createElement(Dialog.Root, {
26
+ modal: false,
27
+ open,
28
+ onOpenChange: setOpen
29
+ }, /* @__PURE__ */ React.createElement("div", {
30
+ className: "dx-dialog__overlay bg-transparent pointer-events-none",
31
+ "data-block-align": "end"
32
+ }, /* @__PURE__ */ React.createElement(Dialog.Content, {
33
+ size: "md",
34
+ inOverlayLayout: true,
35
+ classNames: [
36
+ "grid grid-rows-[var(--dx-rail-action)_1fr_min-content] p-0 overflow-hidden box-content pointer-events-auto"
37
+ ],
38
+ onEscapeKeyDown: onEscape,
39
+ onInteractOutside: preventDefault
40
+ }, children))));
53
41
  };
54
42
  ChatDialogRoot.displayName = "ChatDialog.Root";
43
+ var CHAT_DIALOG_HEADER_NAME = "ChatDialog.Header";
55
44
  var ChatDialogHeader = ({ classNames, title }) => {
56
- var _effect = _useSignals();
57
- try {
58
- const { expanded, setExpanded } = useChatDialogContext(ChatDialogHeader.displayName);
59
- return /* @__PURE__ */ React.createElement("div", {
60
- className: mx("grid grid-cols-[var(--rail-action)_1fr_min-content] items-center overflow-hidden", classNames)
61
- }, /* @__PURE__ */ React.createElement(Endcap, null, /* @__PURE__ */ React.createElement(Dialog.Close, null, /* @__PURE__ */ React.createElement(Icon, {
62
- icon: "ph--x--regular"
63
- }))), /* @__PURE__ */ React.createElement(Dialog.Title, {
64
- classNames: "flex w-full justify-center text-sm text-subdued select-none cursor-pointer",
65
- onClick: () => setExpanded((expanded2) => !expanded2)
66
- }, title), /* @__PURE__ */ React.createElement(Endcap, null, /* @__PURE__ */ React.createElement(IconButton, {
67
- variant: "ghost",
68
- icon: "ph--caret-up--regular",
69
- iconOnly: true,
70
- classNames: mx("!p-1 [&>svg]:transition [&>svg]:duration-200", expanded && "[&>svg]:rotate-180"),
71
- label: expanded ? "Close" : "Open",
72
- onClick: () => setExpanded((expanded2) => !expanded2)
73
- })));
74
- } finally {
75
- _effect.f();
76
- }
45
+ const { expanded, setExpanded } = useChatDialogContext(CHAT_DIALOG_HEADER_NAME);
46
+ return /* @__PURE__ */ React.createElement("div", {
47
+ className: mx("grid grid-cols-[var(--dx-rail-action)_1fr_min-content] items-center overflow-hidden", classNames)
48
+ }, /* @__PURE__ */ React.createElement(Endcap, null, /* @__PURE__ */ React.createElement(Dialog.Close, null, /* @__PURE__ */ React.createElement(Icon, {
49
+ icon: "ph--x--regular"
50
+ }))), /* @__PURE__ */ React.createElement(Dialog.Title, {
51
+ classNames: "flex w-full justify-center text-sm text-subdued select-none cursor-pointer",
52
+ onClick: () => setExpanded((expanded2) => !expanded2)
53
+ }, title), /* @__PURE__ */ React.createElement(Endcap, null, /* @__PURE__ */ React.createElement(IconButton, {
54
+ variant: "ghost",
55
+ icon: "ph--caret-up--regular",
56
+ iconOnly: true,
57
+ classNames: mx("p-1! [&>svg]:transition [&>svg]:duration-200", expanded && "[&>svg]:rotate-180"),
58
+ label: expanded ? "Close" : "Open",
59
+ onClick: () => setExpanded((expanded2) => !expanded2)
60
+ })));
77
61
  };
78
- ChatDialogHeader.displayName = "ChatDialog.Header";
62
+ ChatDialogHeader.displayName = CHAT_DIALOG_HEADER_NAME;
63
+ var CHAT_DIALOG_CONTENT_NAME = "ChatDialog.Content";
79
64
  var ChatDialogContent = ({ children, classNames }) => {
80
- var _effect = _useSignals();
81
- try {
82
- const { expanded, size, setSize } = useChatDialogContext(ChatDialogContent.displayName);
83
- useEffect(() => {
84
- setSize(expanded ? "min-content" : 0);
85
- }, [
86
- expanded
87
- ]);
88
- return /* @__PURE__ */ React.createElement("div", {
89
- className: mx("flex flex-col overflow-y-auto border-t border-b border-subduedSeparator", 'transition-[block-size] ease-in-out duration-0 [&:not([data-dx-resizing="true"])]:duration-200', classNames),
90
- style: {
91
- ...sizeStyle(size, "vertical", true),
92
- maxBlockSize: "calc(100dvh - env(safe-area-inset-bottom) - env(safe-area-inset-top) - 14rem)"
93
- },
94
- ...resizeAttributes
95
- }, /* @__PURE__ */ React.createElement(ResizeHandle, {
96
- side: "block-start",
97
- iconPosition: "center",
98
- defaultSize: "min-content",
99
- fallbackSize: 0,
100
- minSize: 0,
101
- onSizeChange: setSize
102
- }), children);
103
- } finally {
104
- _effect.f();
105
- }
65
+ const { expanded, size, setSize } = useChatDialogContext(CHAT_DIALOG_CONTENT_NAME);
66
+ useEffect(() => {
67
+ setSize(expanded ? "min-content" : 0);
68
+ }, [
69
+ expanded
70
+ ]);
71
+ return /* @__PURE__ */ React.createElement("div", {
72
+ className: mx("border-t border-b border-subdued-separator", 'transition ease-in-out duration-0 [&:not([data-dx-resizing="true"])]:duration-200', classNames),
73
+ style: {
74
+ ...sizeStyle(size, "vertical", true),
75
+ maxBlockSize: "calc(100dvh - env(safe-area-inset-bottom) - env(safe-area-inset-top) - 14rem)"
76
+ },
77
+ ...resizeAttributes
78
+ }, /* @__PURE__ */ React.createElement(ResizeHandle, {
79
+ side: "block-start",
80
+ iconPosition: "center",
81
+ defaultSize: "min-content",
82
+ fallbackSize: 0,
83
+ minSize: 0,
84
+ onSizeChange: setSize
85
+ }), children);
106
86
  };
107
- ChatDialogContent.displayName = "ChatDialog.Content";
87
+ ChatDialogContent.displayName = CHAT_DIALOG_CONTENT_NAME;
88
+ var CHAT_DIALOG_FOOTER_NAME = "ChatDialog.Footer";
108
89
  var ChatDialogFooter = ({ children, classNames }) => {
109
- var _effect = _useSignals();
110
- try {
111
- return /* @__PURE__ */ React.createElement("div", {
112
- className: mx(classNames)
113
- }, children);
114
- } finally {
115
- _effect.f();
116
- }
90
+ return /* @__PURE__ */ React.createElement("div", {
91
+ className: mx(classNames)
92
+ }, children);
117
93
  };
118
- ChatDialogFooter.displayName = "ChatDialog.Footer";
94
+ ChatDialogFooter.displayName = CHAT_DIALOG_FOOTER_NAME;
119
95
  var ChatDialog = {
120
96
  Root: ChatDialogRoot,
121
97
  Header: ChatDialogHeader,
@@ -129,31 +105,23 @@ import { RangeSet } from "@codemirror/state";
129
105
  import { Decoration, EditorView, ViewPlugin, WidgetType, keymap } from "@codemirror/view";
130
106
  import { Mutex } from "@dxos/async";
131
107
  import { invariant } from "@dxos/invariant";
132
- function _define_property(obj, key, value) {
133
- if (key in obj) {
134
- Object.defineProperty(obj, key, {
135
- value,
136
- enumerable: true,
137
- configurable: true,
138
- writable: true
139
- });
140
- } else {
141
- obj[key] = value;
142
- }
143
- return obj;
144
- }
145
108
  var __dxlog_file = "/__w/dxos/dxos/packages/ui/react-ui-chat/src/components/ChatEditor/references.ts";
146
109
  var references = ({ provider, triggerCharacter = "@", debug = false }) => {
147
- invariant(triggerCharacter.length === 1, void 0, {
148
- F: __dxlog_file,
149
- L: 50,
150
- S: void 0,
151
- A: [
152
- "triggerCharacter.length === 1",
153
- ""
154
- ]
155
- });
110
+ invariant(triggerCharacter.length === 1, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 14, S: void 0, A: ["triggerCharacter.length === 1", ""] });
156
111
  const decorationField = ViewPlugin.fromClass(class ReferenceView {
112
+ // TODO(burdon): Why?
113
+ _mutex = new Mutex();
114
+ decorations = Decoration.set([]);
115
+ constructor(view) {
116
+ queueMicrotask(async () => {
117
+ const guard = await this._mutex.acquire();
118
+ try {
119
+ this.decorations = await this._computeDecorations(view);
120
+ } finally {
121
+ guard.release();
122
+ }
123
+ });
124
+ }
157
125
  update(update) {
158
126
  if (update.docChanged) {
159
127
  queueMicrotask(async () => {
@@ -184,21 +152,9 @@ var references = ({ provider, triggerCharacter = "@", debug = false }) => {
184
152
  }
185
153
  return Decoration.set(decorations);
186
154
  }
187
- constructor(view) {
188
- _define_property(this, "_mutex", new Mutex());
189
- _define_property(this, "decorations", Decoration.set([]));
190
- queueMicrotask(async () => {
191
- const guard = await this._mutex.acquire();
192
- try {
193
- this.decorations = await this._computeDecorations(view);
194
- } finally {
195
- guard.release();
196
- }
197
- });
198
- }
199
155
  }, {
200
156
  decorations: (v) => v.decorations,
201
- provide: (plugin) => [
157
+ provide: () => [
202
158
  EditorView.atomicRanges.of((view) => view.plugin(decorationField)?.decorations ?? RangeSet.empty)
203
159
  ]
204
160
  });
@@ -213,7 +169,7 @@ var references = ({ provider, triggerCharacter = "@", debug = false }) => {
213
169
  }
214
170
  }),
215
171
  autocompletion({
216
- tooltipClass: () => "shadow rounded",
172
+ tooltipClass: () => "shadow rounded-sm",
217
173
  activateOnTyping: true,
218
174
  aboveCursor: true,
219
175
  closeOnBlur: !debug,
@@ -242,279 +198,162 @@ var references = ({ provider, triggerCharacter = "@", debug = false }) => {
242
198
  ];
243
199
  };
244
200
  var ReferenceWidget = class extends WidgetType {
245
- toDOM() {
246
- const span = document.createElement("span");
247
- span.textContent = `@ ${this.data.label}`;
248
- span.className = "cm-reference-pill";
249
- return span;
250
- }
251
- eq(other) {
252
- return other.data.uri === this.data.uri;
201
+ data;
202
+ constructor(data) {
203
+ super(), this.data = data;
253
204
  }
254
205
  ignoreEvent() {
255
206
  return true;
256
207
  }
257
- constructor(data) {
258
- super(), _define_property(this, "data", void 0), this.data = data;
259
- }
260
- };
261
-
262
- // src/components/ChatEditor/ChatEditor.tsx
263
- import { useSignals as _useSignals2 } from "@preact-signals/safe-react/tracking";
264
- import React2, { forwardRef, useImperativeHandle } from "react";
265
- import { useThemeContext } from "@dxos/react-ui";
266
- import { createBasicExtensions, createThemeExtensions, useTextEditor } from "@dxos/react-ui-editor";
267
- import { mx as mx2 } from "@dxos/react-ui-theme";
268
- import { isNonNullable } from "@dxos/util";
269
-
270
- // src/components/ChatEditor/autocomplete.ts
271
- import { Prec } from "@codemirror/state";
272
- import { Decoration as Decoration2, EditorView as EditorView2, ViewPlugin as ViewPlugin2, WidgetType as WidgetType2, keymap as keymap2 } from "@codemirror/view";
273
- function _define_property2(obj, key, value) {
274
- if (key in obj) {
275
- Object.defineProperty(obj, key, {
276
- value,
277
- enumerable: true,
278
- configurable: true,
279
- writable: true
280
- });
281
- } else {
282
- obj[key] = value;
208
+ eq(other) {
209
+ return this.data.uri === other.data.uri;
283
210
  }
284
- return obj;
285
- }
286
- var autocomplete = ({ fireIfEmpty, onSubmit, onSuggest, onCancel }) => {
287
- const suggest = ViewPlugin2.fromClass(class {
288
- update(update) {
289
- if (update.docChanged || update.selectionSet) {
290
- this._decorations = this.computeDecorations(update.view);
291
- }
292
- }
293
- computeDecorations(view) {
294
- const text = view.state.doc.toString();
295
- const suggestions = onSuggest?.(text) ?? [];
296
- if (!suggestions.length) {
297
- this._currentSuggestion = null;
298
- return Decoration2.none;
299
- }
300
- this._currentSuggestion = suggestions[0];
301
- const suffix = this._currentSuggestion.slice(text.length);
302
- if (!suffix) {
303
- return Decoration2.none;
304
- }
305
- return Decoration2.set([
306
- Decoration2.widget({
307
- widget: new InlineSuggestionWidget(suffix),
308
- side: 1
309
- }).range(view.state.doc.length)
310
- ]);
311
- }
312
- completeSuggestion(view) {
313
- if (!this._currentSuggestion) {
314
- return false;
315
- }
316
- const text = view.state.doc.toString();
317
- const suffix = this._currentSuggestion.slice(text.length);
318
- if (!suffix) {
319
- return false;
320
- }
321
- view.dispatch({
322
- changes: {
323
- from: view.state.doc.length,
324
- insert: suffix
325
- },
326
- selection: {
327
- anchor: view.state.doc.length + suffix.length
328
- }
329
- });
330
- return true;
331
- }
332
- constructor(view) {
333
- _define_property2(this, "_decorations", void 0);
334
- _define_property2(this, "_currentSuggestion", null);
335
- this._decorations = this.computeDecorations(view);
336
- }
337
- }, {
338
- decorations: (v) => v._decorations
339
- });
340
- return [
341
- suggest,
342
- EditorView2.theme({
343
- ".cm-inline-suggestion": {
344
- opacity: 0.4
345
- }
346
- }),
347
- Prec.highest(keymap2.of([
348
- {
349
- key: "Tab",
350
- preventDefault: true,
351
- run: (view) => {
352
- const plugin = view.plugin(suggest);
353
- return plugin?.completeSuggestion(view) ?? false;
354
- }
355
- },
356
- {
357
- key: "ArrowRight",
358
- preventDefault: true,
359
- run: (view) => {
360
- if (view.state.selection.main.head !== view.state.doc.length) {
361
- return false;
362
- }
363
- const plugin = view.plugin(suggest);
364
- return plugin?.completeSuggestion(view) ?? false;
365
- }
366
- },
367
- {
368
- key: "Enter",
369
- preventDefault: true,
370
- run: (view) => {
371
- const text = view.state.doc.toString().trim();
372
- if (onSubmit && (fireIfEmpty || text.length > 0)) {
373
- const reset = onSubmit(text);
374
- if (reset) {
375
- view.dispatch({
376
- changes: {
377
- from: 0,
378
- to: view.state.doc.length,
379
- insert: ""
380
- }
381
- });
382
- }
383
- }
384
- return true;
385
- }
386
- },
387
- {
388
- key: "Shift-Enter",
389
- preventDefault: true,
390
- run: (view) => {
391
- view.dispatch({
392
- changes: {
393
- from: view.state.selection.main.head,
394
- insert: "\n"
395
- },
396
- selection: {
397
- anchor: view.state.selection.main.head + 1,
398
- head: view.state.selection.main.head + 1
399
- }
400
- });
401
- return true;
402
- }
403
- },
404
- {
405
- key: "Escape",
406
- preventDefault: true,
407
- run: (view) => {
408
- view.dispatch({
409
- changes: {
410
- from: 0,
411
- to: view.state.doc.length,
412
- insert: ""
413
- }
414
- });
415
- onCancel?.();
416
- return true;
417
- }
418
- }
419
- ]))
420
- ];
421
- };
422
- var InlineSuggestionWidget = class extends WidgetType2 {
423
211
  toDOM() {
424
212
  const span = document.createElement("span");
425
- span.textContent = this.suffix;
426
- span.className = "cm-inline-suggestion";
213
+ span.textContent = `@${this.data.label}`;
214
+ span.className = "cm-reference-pill";
427
215
  return span;
428
216
  }
429
- eq(other) {
430
- return other.suffix === this.suffix;
431
- }
432
- constructor(suffix) {
433
- super(), _define_property2(this, "suffix", void 0), this.suffix = suffix;
434
- }
435
217
  };
436
218
 
437
219
  // src/components/ChatEditor/ChatEditor.tsx
438
- var ChatEditor = /* @__PURE__ */ forwardRef(({ classNames, extensions, references: references2, autoFocus, lineWrapping = false, placeholder, onSubmit, onSuggest, onCancel }, forwardRef2) => {
439
- var _effect = _useSignals2();
440
- try {
441
- const { themeMode } = useThemeContext();
442
- const { parentRef, view } = useTextEditor(() => ({
443
- debug: true,
444
- autoFocus,
445
- extensions: [
446
- createThemeExtensions({
447
- themeMode
448
- }),
449
- autocomplete({
450
- onSubmit,
451
- onSuggest,
452
- onCancel
453
- }),
454
- references2 ? references({
455
- provider: references2.provider
456
- }) : [],
457
- createBasicExtensions({
458
- bracketMatching: false,
459
- lineWrapping,
460
- placeholder
461
- }),
462
- extensions
463
- ].filter(isNonNullable)
464
- }), [
465
- themeMode,
466
- extensions,
467
- onSubmit,
468
- onSuggest,
469
- onCancel
470
- ]);
471
- useImperativeHandle(forwardRef2, () => {
472
- return {
473
- focus: () => {
474
- view?.focus();
475
- },
476
- getText: () => view?.state.doc.toString() ?? "",
477
- setText: (text) => {
478
- view?.dispatch({
479
- changes: {
480
- from: 0,
481
- to: view.state.doc.length,
482
- insert: text
483
- },
484
- selection: {
485
- anchor: text.length,
486
- head: text.length
487
- }
488
- });
489
- }
490
- };
491
- }, [
492
- view,
220
+ import React2, { forwardRef, useMemo } from "react";
221
+ import { useThemeContext } from "@dxos/react-ui";
222
+ import { Editor } from "@dxos/react-ui-editor";
223
+ import { createBasicExtensions, createThemeExtensions, submit, xmlFormatting } from "@dxos/ui-editor";
224
+ import { isTruthy } from "@dxos/util";
225
+ var useChatExtensions = ({ extensions, lineWrapping = false, placeholder, onSubmit }) => {
226
+ const { themeMode } = useThemeContext();
227
+ return useMemo(() => [
228
+ createThemeExtensions({
229
+ themeMode
230
+ }),
231
+ createBasicExtensions({
232
+ bracketMatching: false,
233
+ lineWrapping,
234
+ placeholder
235
+ }),
236
+ xmlFormatting(),
237
+ submit({
493
238
  onSubmit
494
- ]);
495
- return /* @__PURE__ */ React2.createElement("div", {
496
- ref: parentRef,
497
- className: mx2("is-full", classNames)
498
- });
499
- } finally {
500
- _effect.f();
501
- }
239
+ }),
240
+ extensions
241
+ ].filter(isTruthy), [
242
+ themeMode,
243
+ extensions,
244
+ onSubmit
245
+ ]);
246
+ };
247
+ var ChatEditor = /* @__PURE__ */ forwardRef(({ classNames, autoFocus, extensions: extensionsProp, lineWrapping = false, placeholder, onSubmit }, forwardRef3) => {
248
+ const extensions = useChatExtensions({
249
+ extensions: extensionsProp,
250
+ lineWrapping,
251
+ placeholder,
252
+ onSubmit
253
+ });
254
+ return /* @__PURE__ */ React2.createElement(Editor.Root, {
255
+ ref: forwardRef3
256
+ }, /* @__PURE__ */ React2.createElement(Editor.View, {
257
+ classNames,
258
+ autoFocus,
259
+ extensions
260
+ }));
502
261
  });
503
262
 
504
- // src/translations.ts
505
- var translationKey = "react-ui-chat";
506
- var translations = [
507
- {
508
- "en-US": {
509
- [translationKey]: {}
263
+ // src/components/ChatStatus/ChatStatus.tsx
264
+ import { createContext as createContext2 } from "@radix-ui/react-context";
265
+ import React3, { forwardRef as forwardRef2, useEffect as useEffect2, useImperativeHandle, useState as useState2 } from "react";
266
+ import { mx as mx2 } from "@dxos/ui-theme";
267
+
268
+ // src/components/ChatStatus/format.ts
269
+ var SECOND = 1e3;
270
+ var MINUTE = 60 * SECOND;
271
+ var HOUR = 60 * MINUTE;
272
+ var formatElapsed = (ms) => {
273
+ const safe = Math.max(0, ms);
274
+ if (safe < MINUTE) {
275
+ return `${Math.floor(safe / SECOND)}s`;
276
+ }
277
+ if (safe < HOUR) {
278
+ const m2 = Math.floor(safe / MINUTE);
279
+ const s = Math.floor(safe % MINUTE / SECOND);
280
+ return `${m2}m ${s}s`;
281
+ }
282
+ const h = Math.floor(safe / HOUR);
283
+ const m = Math.floor(safe % HOUR / MINUTE);
284
+ return `${h}h ${m}m`;
285
+ };
286
+
287
+ // src/components/ChatStatus/ChatStatus.tsx
288
+ var TICK_MS = 1e3;
289
+ var [ChatStatusProvider, useChatStatusContext] = createContext2("ChatStatus");
290
+ var Root = /* @__PURE__ */ forwardRef2(({ classNames, children, defaultRunning = true }, forwardedRef) => {
291
+ const [elapsed, setElapsed] = useState2(0);
292
+ const [running, setRunning] = useState2(defaultRunning);
293
+ useEffect2(() => {
294
+ if (!running) {
295
+ return;
510
296
  }
297
+ const startedAt = Date.now() - elapsed * TICK_MS;
298
+ let timeout;
299
+ const tick = () => {
300
+ setElapsed(Math.floor((Date.now() - startedAt) / TICK_MS));
301
+ timeout = setTimeout(tick, TICK_MS - Date.now() % TICK_MS);
302
+ };
303
+ timeout = setTimeout(tick, TICK_MS - Date.now() % TICK_MS);
304
+ return () => clearTimeout(timeout);
305
+ }, [
306
+ running
307
+ ]);
308
+ useImperativeHandle(forwardedRef, () => ({
309
+ start: () => setRunning(true),
310
+ stop: () => setRunning(false)
311
+ }), []);
312
+ return /* @__PURE__ */ React3.createElement(ChatStatusProvider, {
313
+ elapsed,
314
+ running
315
+ }, /* @__PURE__ */ React3.createElement("span", {
316
+ className: mx2("inline-flex items-center gap-2 text-description font-mono tabular-nums", classNames)
317
+ }, children));
318
+ });
319
+ Root.displayName = "ChatChatStatus.Root";
320
+ var Icon2 = ({ classNames, children }) => {
321
+ if (children !== void 0) {
322
+ return /* @__PURE__ */ React3.createElement(React3.Fragment, null, children);
511
323
  }
512
- ];
324
+ return /* @__PURE__ */ React3.createElement("span", {
325
+ "aria-hidden": "true",
326
+ className: mx2("inline-block size-2 rounded-full bg-current animate-halo-pulse", classNames)
327
+ });
328
+ };
329
+ var Stopwatch = ({ classNames, offset = 0 }) => {
330
+ const { elapsed } = useChatStatusContext("ChatChatStatus.Stopwatch");
331
+ return /* @__PURE__ */ React3.createElement("span", {
332
+ "aria-live": "off",
333
+ className: mx2(classNames)
334
+ }, formatElapsed((elapsed + offset) * 1e3));
335
+ };
336
+ var Separator = ({ classNames }) => /* @__PURE__ */ React3.createElement("span", {
337
+ "aria-hidden": "true",
338
+ className: mx2("opacity-50", classNames)
339
+ }, "\xB7");
340
+ var Text = ({ classNames, children }) => /* @__PURE__ */ React3.createElement("span", {
341
+ className: mx2(classNames)
342
+ }, children);
343
+ var ChatStatus = {
344
+ Root,
345
+ Icon: Icon2,
346
+ Stopwatch,
347
+ Separator,
348
+ Text
349
+ };
513
350
  export {
514
351
  ChatDialog,
515
352
  ChatEditor,
353
+ ChatStatus,
354
+ formatElapsed,
516
355
  references,
517
- translationKey,
518
- translations
356
+ useChatExtensions,
357
+ useChatStatusContext
519
358
  };
520
359
  //# sourceMappingURL=index.mjs.map