@dxos/react-ui-chat 0.8.4-main.c4373fc → 0.8.4-main.d05539e30a

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