@dxos/react-ui-chat 0.8.4-main.a4bbb77 → 0.8.4-main.abd8ff62ef

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 (51) hide show
  1. package/dist/lib/browser/index.mjs +232 -250
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/browser/translations.mjs +14 -0
  5. package/dist/lib/browser/translations.mjs.map +7 -0
  6. package/dist/lib/node-esm/index.mjs +232 -250
  7. package/dist/lib/node-esm/index.mjs.map +4 -4
  8. package/dist/lib/node-esm/meta.json +1 -1
  9. package/dist/lib/node-esm/translations.mjs +16 -0
  10. package/dist/lib/node-esm/translations.mjs.map +7 -0
  11. package/dist/types/src/components/ChatDialog/ChatDialog.d.ts +17 -1
  12. package/dist/types/src/components/ChatDialog/ChatDialog.d.ts.map +1 -1
  13. package/dist/types/src/components/ChatDialog/ChatDialog.stories.d.ts +2 -3
  14. package/dist/types/src/components/ChatDialog/ChatDialog.stories.d.ts.map +1 -1
  15. package/dist/types/src/components/ChatEditor/ChatEditor.d.ts +7 -8
  16. package/dist/types/src/components/ChatEditor/ChatEditor.d.ts.map +1 -1
  17. package/dist/types/src/components/ChatEditor/ChatEditor.stories.d.ts +3 -3
  18. package/dist/types/src/components/ChatEditor/ChatEditor.stories.d.ts.map +1 -1
  19. package/dist/types/src/components/ChatEditor/references.d.ts +4 -0
  20. package/dist/types/src/components/ChatEditor/references.d.ts.map +1 -1
  21. package/dist/types/src/components/ChatStatus/ChatStatus.d.ts +40 -0
  22. package/dist/types/src/components/ChatStatus/ChatStatus.d.ts.map +1 -0
  23. package/dist/types/src/components/ChatStatus/ChatStatus.stories.d.ts +27 -0
  24. package/dist/types/src/components/ChatStatus/ChatStatus.stories.d.ts.map +1 -0
  25. package/dist/types/src/components/ChatStatus/format.d.ts +7 -0
  26. package/dist/types/src/components/ChatStatus/format.d.ts.map +1 -0
  27. package/dist/types/src/components/ChatStatus/format.test.d.ts +2 -0
  28. package/dist/types/src/components/ChatStatus/format.test.d.ts.map +1 -0
  29. package/dist/types/src/components/ChatStatus/index.d.ts +3 -0
  30. package/dist/types/src/components/ChatStatus/index.d.ts.map +1 -0
  31. package/dist/types/src/components/index.d.ts +1 -0
  32. package/dist/types/src/components/index.d.ts.map +1 -1
  33. package/dist/types/src/index.d.ts +0 -1
  34. package/dist/types/src/index.d.ts.map +1 -1
  35. package/dist/types/src/translations.d.ts +2 -2
  36. package/dist/types/src/translations.d.ts.map +1 -1
  37. package/dist/types/tsconfig.tsbuildinfo +1 -1
  38. package/package.json +46 -38
  39. package/src/components/ChatDialog/ChatDialog.stories.tsx +4 -5
  40. package/src/components/ChatDialog/ChatDialog.tsx +27 -17
  41. package/src/components/ChatEditor/ChatEditor.stories.tsx +19 -17
  42. package/src/components/ChatEditor/ChatEditor.tsx +33 -85
  43. package/src/components/ChatEditor/references.ts +18 -12
  44. package/src/components/ChatStatus/ChatStatus.stories.tsx +130 -0
  45. package/src/components/ChatStatus/ChatStatus.tsx +183 -0
  46. package/src/components/ChatStatus/format.test.ts +48 -0
  47. package/src/components/ChatStatus/format.ts +27 -0
  48. package/src/components/ChatStatus/index.ts +6 -0
  49. package/src/components/index.ts +1 -0
  50. package/src/index.ts +0 -1
  51. package/src/translations.ts +1 -1
@@ -1,121 +1,98 @@
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: expandedParam = 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(expandedParam, 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
+ role: "none",
31
+ className: "dx-dialog__overlay bg-transparent pointer-events-none",
32
+ "data-block-align": "end"
33
+ }, /* @__PURE__ */ React.createElement(Dialog.Content, {
34
+ size: "md",
35
+ inOverlayLayout: true,
36
+ classNames: [
37
+ "grid grid-rows-[var(--dx-rail-action)_1fr_min-content] p-0 overflow-hidden box-content pointer-events-auto"
38
+ ],
39
+ onEscapeKeyDown: onEscape,
40
+ onInteractOutside: preventDefault
41
+ }, children))));
53
42
  };
54
43
  ChatDialogRoot.displayName = "ChatDialog.Root";
44
+ var CHAT_DIALOG_HEADER_NAME = "ChatDialog.Header";
55
45
  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
- }
46
+ const { expanded, setExpanded } = useChatDialogContext(CHAT_DIALOG_HEADER_NAME);
47
+ return /* @__PURE__ */ React.createElement("div", {
48
+ className: mx("grid grid-cols-[var(--dx-rail-action)_1fr_min-content] items-center overflow-hidden", classNames)
49
+ }, /* @__PURE__ */ React.createElement(Endcap, null, /* @__PURE__ */ React.createElement(Dialog.Close, null, /* @__PURE__ */ React.createElement(Icon, {
50
+ icon: "ph--x--regular"
51
+ }))), /* @__PURE__ */ React.createElement(Dialog.Title, {
52
+ classNames: "flex w-full justify-center text-sm text-subdued select-none cursor-pointer",
53
+ onClick: () => setExpanded((expanded2) => !expanded2)
54
+ }, title), /* @__PURE__ */ React.createElement(Endcap, null, /* @__PURE__ */ React.createElement(IconButton, {
55
+ variant: "ghost",
56
+ icon: "ph--caret-up--regular",
57
+ iconOnly: true,
58
+ classNames: mx("p-1! [&>svg]:transition [&>svg]:duration-200", expanded && "[&>svg]:rotate-180"),
59
+ label: expanded ? "Close" : "Open",
60
+ onClick: () => setExpanded((expanded2) => !expanded2)
61
+ })));
77
62
  };
78
- ChatDialogHeader.displayName = "ChatDialog.Header";
63
+ ChatDialogHeader.displayName = CHAT_DIALOG_HEADER_NAME;
64
+ var CHAT_DIALOG_CONTENT_NAME = "ChatDialog.Content";
79
65
  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
- }
66
+ const { expanded, size, setSize } = useChatDialogContext(CHAT_DIALOG_CONTENT_NAME);
67
+ useEffect(() => {
68
+ setSize(expanded ? "min-content" : 0);
69
+ }, [
70
+ expanded
71
+ ]);
72
+ return /* @__PURE__ */ React.createElement("div", {
73
+ className: mx("border-t border-b border-subdued-separator", 'transition ease-in-out duration-0 [&:not([data-dx-resizing="true"])]:duration-200', classNames),
74
+ style: {
75
+ ...sizeStyle(size, "vertical", true),
76
+ maxBlockSize: "calc(100dvh - env(safe-area-inset-bottom) - env(safe-area-inset-top) - 14rem)"
77
+ },
78
+ ...resizeAttributes
79
+ }, /* @__PURE__ */ React.createElement(ResizeHandle, {
80
+ side: "block-start",
81
+ iconPosition: "center",
82
+ defaultSize: "min-content",
83
+ fallbackSize: 0,
84
+ minSize: 0,
85
+ onSizeChange: setSize
86
+ }), children);
106
87
  };
107
- ChatDialogContent.displayName = "ChatDialog.Content";
88
+ ChatDialogContent.displayName = CHAT_DIALOG_CONTENT_NAME;
89
+ var CHAT_DIALOG_FOOTER_NAME = "ChatDialog.Footer";
108
90
  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
- }
91
+ return /* @__PURE__ */ React.createElement("div", {
92
+ className: mx(classNames)
93
+ }, children);
117
94
  };
118
- ChatDialogFooter.displayName = "ChatDialog.Footer";
95
+ ChatDialogFooter.displayName = CHAT_DIALOG_FOOTER_NAME;
119
96
  var ChatDialog = {
120
97
  Root: ChatDialogRoot,
121
98
  Header: ChatDialogHeader,
@@ -129,31 +106,23 @@ import { RangeSet } from "@codemirror/state";
129
106
  import { Decoration, EditorView, ViewPlugin, WidgetType, keymap } from "@codemirror/view";
130
107
  import { Mutex } from "@dxos/async";
131
108
  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
109
  var __dxlog_file = "/__w/dxos/dxos/packages/ui/react-ui-chat/src/components/ChatEditor/references.ts";
146
110
  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
- });
111
+ invariant(triggerCharacter.length === 1, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 14, S: void 0, A: ["triggerCharacter.length === 1", ""] });
156
112
  const decorationField = ViewPlugin.fromClass(class ReferenceView {
113
+ // TODO(burdon): Why?
114
+ _mutex = new Mutex();
115
+ decorations = Decoration.set([]);
116
+ constructor(view) {
117
+ queueMicrotask(async () => {
118
+ const guard = await this._mutex.acquire();
119
+ try {
120
+ this.decorations = await this._computeDecorations(view);
121
+ } finally {
122
+ guard.release();
123
+ }
124
+ });
125
+ }
157
126
  update(update) {
158
127
  if (update.docChanged) {
159
128
  queueMicrotask(async () => {
@@ -184,21 +153,9 @@ var references = ({ provider, triggerCharacter = "@", debug = false }) => {
184
153
  }
185
154
  return Decoration.set(decorations);
186
155
  }
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
156
  }, {
200
157
  decorations: (v) => v.decorations,
201
- provide: (plugin) => [
158
+ provide: () => [
202
159
  EditorView.atomicRanges.of((view) => view.plugin(decorationField)?.decorations ?? RangeSet.empty)
203
160
  ]
204
161
  });
@@ -213,7 +170,7 @@ var references = ({ provider, triggerCharacter = "@", debug = false }) => {
213
170
  }
214
171
  }),
215
172
  autocompletion({
216
- tooltipClass: () => "shadow rounded",
173
+ tooltipClass: () => "shadow rounded-sm",
217
174
  activateOnTyping: true,
218
175
  aboveCursor: true,
219
176
  closeOnBlur: !debug,
@@ -242,137 +199,162 @@ var references = ({ provider, triggerCharacter = "@", debug = false }) => {
242
199
  ];
243
200
  };
244
201
  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;
202
+ data;
203
+ constructor(data) {
204
+ super(), this.data = data;
253
205
  }
254
206
  ignoreEvent() {
255
207
  return true;
256
208
  }
257
- constructor(data) {
258
- super(), _define_property(this, "data", void 0), this.data = data;
209
+ eq(other) {
210
+ return this.data.uri === other.data.uri;
211
+ }
212
+ toDOM() {
213
+ const span = document.createElement("span");
214
+ span.textContent = `@${this.data.label}`;
215
+ span.className = "cm-reference-pill";
216
+ return span;
259
217
  }
260
218
  };
261
219
 
262
220
  // src/components/ChatEditor/ChatEditor.tsx
263
- import { useSignals as _useSignals2 } from "@preact-signals/safe-react/tracking";
264
- import { keymap as keymap2 } from "@codemirror/view";
265
- import { useFocusFinders } from "@fluentui/react-tabster";
266
- import React2, { forwardRef, useImperativeHandle } from "react";
221
+ import React2, { forwardRef, useMemo } from "react";
267
222
  import { useThemeContext } from "@dxos/react-ui";
268
- import { autocomplete, createBasicExtensions, createThemeExtensions, useTextEditor } from "@dxos/react-ui-editor";
269
- import { mx as mx2 } from "@dxos/react-ui-theme";
223
+ import { Editor } from "@dxos/react-ui-editor";
224
+ import { createBasicExtensions, createThemeExtensions, submit, xmlFormatting } from "@dxos/ui-editor";
270
225
  import { isTruthy } from "@dxos/util";
271
- var ChatEditor = /* @__PURE__ */ forwardRef(({ classNames, extensions, references: references2, autoFocus, lineWrapping = false, placeholder, onSubmit, onSuggest, onCancel }, forwardRef2) => {
272
- var _effect = _useSignals2();
273
- try {
274
- const { themeMode } = useThemeContext();
275
- const { findNextFocusable, findPrevFocusable } = useFocusFinders();
276
- const { parentRef, view } = useTextEditor(() => ({
277
- debug: true,
278
- autoFocus,
279
- extensions: [
280
- createThemeExtensions({
281
- themeMode
282
- }),
283
- createBasicExtensions({
284
- bracketMatching: false,
285
- lineWrapping,
286
- placeholder
287
- }),
288
- createBasicExtensions({
289
- bracketMatching: false,
290
- lineWrapping,
291
- placeholder
292
- }),
293
- autocomplete({
294
- onSubmit,
295
- onSuggest,
296
- onCancel
297
- }),
298
- references2 ? references({
299
- provider: references2.provider
300
- }) : [],
301
- // TODO(burdon): Standardize.
302
- keymap2.of([
303
- {
304
- key: "Tab",
305
- preventDefault: true,
306
- run: (view2) => {
307
- findNextFocusable(view2.dom)?.focus();
308
- return true;
309
- }
310
- },
311
- {
312
- key: "Shift-Tab",
313
- preventDefault: true,
314
- run: (view2) => {
315
- findPrevFocusable(view2.dom)?.focus();
316
- return true;
317
- }
318
- }
319
- ]),
320
- extensions
321
- ].filter(isTruthy)
322
- }), [
323
- themeMode,
324
- extensions,
325
- onSubmit,
326
- onSuggest,
327
- onCancel
328
- ]);
329
- useImperativeHandle(forwardRef2, () => {
330
- return {
331
- focus: () => {
332
- view?.focus();
333
- },
334
- getText: () => view?.state.doc.toString() ?? "",
335
- setText: (text) => {
336
- view?.dispatch({
337
- changes: {
338
- from: 0,
339
- to: view.state.doc.length,
340
- insert: text
341
- },
342
- selection: {
343
- anchor: text.length,
344
- head: text.length
345
- }
346
- });
347
- }
348
- };
349
- }, [
350
- view,
226
+ var useChatExtensions = ({ extensions, lineWrapping = false, placeholder, onSubmit }) => {
227
+ const { themeMode } = useThemeContext();
228
+ return useMemo(() => [
229
+ createThemeExtensions({
230
+ themeMode
231
+ }),
232
+ createBasicExtensions({
233
+ bracketMatching: false,
234
+ lineWrapping,
235
+ placeholder
236
+ }),
237
+ xmlFormatting(),
238
+ submit({
351
239
  onSubmit
352
- ]);
353
- return /* @__PURE__ */ React2.createElement("div", {
354
- ref: parentRef,
355
- className: mx2("is-full", classNames)
356
- });
357
- } finally {
358
- _effect.f();
359
- }
240
+ }),
241
+ extensions
242
+ ].filter(isTruthy), [
243
+ themeMode,
244
+ extensions,
245
+ onSubmit
246
+ ]);
247
+ };
248
+ var ChatEditor = /* @__PURE__ */ forwardRef(({ classNames, autoFocus, extensions: extensionsProp, lineWrapping = false, placeholder, onSubmit }, forwardRef3) => {
249
+ const extensions = useChatExtensions({
250
+ extensions: extensionsProp,
251
+ lineWrapping,
252
+ placeholder,
253
+ onSubmit
254
+ });
255
+ return /* @__PURE__ */ React2.createElement(Editor.Root, {
256
+ ref: forwardRef3
257
+ }, /* @__PURE__ */ React2.createElement(Editor.View, {
258
+ classNames,
259
+ autoFocus,
260
+ extensions
261
+ }));
360
262
  });
361
263
 
362
- // src/translations.ts
363
- var translationKey = "react-ui-chat";
364
- var translations = [
365
- {
366
- "en-US": {
367
- [translationKey]: {}
264
+ // src/components/ChatStatus/ChatStatus.tsx
265
+ import { createContext as createContext2 } from "@radix-ui/react-context";
266
+ import React3, { forwardRef as forwardRef2, useEffect as useEffect2, useImperativeHandle, useState as useState2 } from "react";
267
+ import { mx as mx2 } from "@dxos/ui-theme";
268
+
269
+ // src/components/ChatStatus/format.ts
270
+ var SECOND = 1e3;
271
+ var MINUTE = 60 * SECOND;
272
+ var HOUR = 60 * MINUTE;
273
+ var formatElapsed = (ms) => {
274
+ const safe = Math.max(0, ms);
275
+ if (safe < MINUTE) {
276
+ return `${Math.floor(safe / SECOND)}s`;
277
+ }
278
+ if (safe < HOUR) {
279
+ const m2 = Math.floor(safe / MINUTE);
280
+ const s = Math.floor(safe % MINUTE / SECOND);
281
+ return `${m2}m ${s}s`;
282
+ }
283
+ const h = Math.floor(safe / HOUR);
284
+ const m = Math.floor(safe % HOUR / MINUTE);
285
+ return `${h}h ${m}m`;
286
+ };
287
+
288
+ // src/components/ChatStatus/ChatStatus.tsx
289
+ var TICK_MS = 1e3;
290
+ var [ChatStatusProvider, useChatStatusContext] = createContext2("ChatStatus");
291
+ var Root = /* @__PURE__ */ forwardRef2(({ classNames, children, defaultRunning = true }, forwardedRef) => {
292
+ const [elapsed, setElapsed] = useState2(0);
293
+ const [running, setRunning] = useState2(defaultRunning);
294
+ useEffect2(() => {
295
+ if (!running) {
296
+ return;
368
297
  }
298
+ const startedAt = Date.now() - elapsed * TICK_MS;
299
+ let timeout;
300
+ const tick = () => {
301
+ setElapsed(Math.floor((Date.now() - startedAt) / TICK_MS));
302
+ timeout = setTimeout(tick, TICK_MS - Date.now() % TICK_MS);
303
+ };
304
+ timeout = setTimeout(tick, TICK_MS - Date.now() % TICK_MS);
305
+ return () => clearTimeout(timeout);
306
+ }, [
307
+ running
308
+ ]);
309
+ useImperativeHandle(forwardedRef, () => ({
310
+ start: () => setRunning(true),
311
+ stop: () => setRunning(false)
312
+ }), []);
313
+ return /* @__PURE__ */ React3.createElement(ChatStatusProvider, {
314
+ elapsed,
315
+ running
316
+ }, /* @__PURE__ */ React3.createElement("span", {
317
+ className: mx2("inline-flex items-center gap-2 text-description font-mono tabular-nums", classNames)
318
+ }, children));
319
+ });
320
+ Root.displayName = "ChatChatStatus.Root";
321
+ var Icon2 = ({ classNames, children }) => {
322
+ if (children !== void 0) {
323
+ return /* @__PURE__ */ React3.createElement(React3.Fragment, null, children);
369
324
  }
370
- ];
325
+ return /* @__PURE__ */ React3.createElement("span", {
326
+ "aria-hidden": "true",
327
+ className: mx2("inline-block size-2 rounded-full bg-current animate-halo-pulse", classNames)
328
+ });
329
+ };
330
+ var Stopwatch = ({ classNames, offset = 0 }) => {
331
+ const { elapsed } = useChatStatusContext("ChatChatStatus.Stopwatch");
332
+ return /* @__PURE__ */ React3.createElement("span", {
333
+ "aria-live": "off",
334
+ className: mx2(classNames)
335
+ }, formatElapsed((elapsed + offset) * 1e3));
336
+ };
337
+ var Separator = ({ classNames }) => /* @__PURE__ */ React3.createElement("span", {
338
+ "aria-hidden": "true",
339
+ className: mx2("opacity-50", classNames)
340
+ }, "\xB7");
341
+ var Text = ({ classNames, children }) => /* @__PURE__ */ React3.createElement("span", {
342
+ className: mx2(classNames)
343
+ }, children);
344
+ var ChatStatus = {
345
+ Root,
346
+ Icon: Icon2,
347
+ Stopwatch,
348
+ Separator,
349
+ Text
350
+ };
371
351
  export {
372
352
  ChatDialog,
373
353
  ChatEditor,
354
+ ChatStatus,
355
+ formatElapsed,
374
356
  references,
375
- translationKey,
376
- translations
357
+ useChatExtensions,
358
+ useChatStatusContext
377
359
  };
378
360
  //# sourceMappingURL=index.mjs.map