@dxos/react-ui-feed 0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/dist/lib/chunk-src.mjs +162 -79
  2. package/dist/lib/chunk-src.mjs.map +1 -1
  3. package/dist/lib/debug.mjs +3 -3
  4. package/dist/lib/debug.mjs.map +1 -1
  5. package/dist/lib/testing.mjs +168 -150
  6. package/dist/lib/testing.mjs.map +1 -1
  7. package/dist/types/src/components/Block/MarkdownBlock.d.ts +8 -5
  8. package/dist/types/src/components/Block/MarkdownBlock.d.ts.map +1 -1
  9. package/dist/types/src/components/Block/diff-range.d.ts +12 -0
  10. package/dist/types/src/components/Block/diff-range.d.ts.map +1 -0
  11. package/dist/types/src/components/Block/diff-range.test.d.ts +2 -0
  12. package/dist/types/src/components/Block/diff-range.test.d.ts.map +1 -0
  13. package/dist/types/src/components/Block/extensions.d.ts +5 -3
  14. package/dist/types/src/components/Block/extensions.d.ts.map +1 -1
  15. package/dist/types/src/components/Block/index.d.ts +5 -5
  16. package/dist/types/src/components/Block/index.d.ts.map +1 -1
  17. package/dist/types/src/components/MessageList/MessageList.d.ts +22 -7
  18. package/dist/types/src/components/MessageList/MessageList.d.ts.map +1 -1
  19. package/dist/types/src/components/MessageList/MessageList.stories.d.ts +1 -1
  20. package/dist/types/src/components/MessageList/MessageList.stories.d.ts.map +1 -1
  21. package/dist/types/src/components/MessageList/index.d.ts +2 -2
  22. package/dist/types/src/components/MessageList/index.d.ts.map +1 -1
  23. package/dist/types/src/components/Minimap/Minimap.d.ts +2 -2
  24. package/dist/types/src/components/Minimap/Minimap.d.ts.map +1 -1
  25. package/dist/types/src/components/Minimap/index.d.ts +1 -1
  26. package/dist/types/src/components/Minimap/index.d.ts.map +1 -1
  27. package/dist/types/src/components/Outline/Outline.d.ts +2 -2
  28. package/dist/types/src/components/Outline/Outline.d.ts.map +1 -1
  29. package/dist/types/src/components/Outline/Outline.stories.d.ts +8 -1
  30. package/dist/types/src/components/Outline/Outline.stories.d.ts.map +1 -1
  31. package/dist/types/src/components/Outline/index.d.ts +1 -1
  32. package/dist/types/src/components/Outline/index.d.ts.map +1 -1
  33. package/dist/types/src/components/index.d.ts +4 -4
  34. package/dist/types/src/components/index.d.ts.map +1 -1
  35. package/dist/types/src/debug/Debug.d.ts +2 -2
  36. package/dist/types/src/debug/Debug.d.ts.map +1 -1
  37. package/dist/types/src/debug/Stats.d.ts +2 -2
  38. package/dist/types/src/debug/Stats.d.ts.map +1 -1
  39. package/dist/types/src/debug/index.d.ts +5 -5
  40. package/dist/types/src/debug/index.d.ts.map +1 -1
  41. package/dist/types/src/debug/useDebug.d.ts +1 -1
  42. package/dist/types/src/debug/useDebug.d.ts.map +1 -1
  43. package/dist/types/src/hooks/index.d.ts +3 -3
  44. package/dist/types/src/hooks/index.d.ts.map +1 -1
  45. package/dist/types/src/hooks/useFeedNavigation.d.ts +1 -1
  46. package/dist/types/src/hooks/useFeedNavigation.d.ts.map +1 -1
  47. package/dist/types/src/index.d.ts +3 -3
  48. package/dist/types/src/index.d.ts.map +1 -1
  49. package/dist/types/src/model/feed.d.ts +1 -1
  50. package/dist/types/src/model/feed.d.ts.map +1 -1
  51. package/dist/types/src/model/index.d.ts +3 -3
  52. package/dist/types/src/model/index.d.ts.map +1 -1
  53. package/dist/types/src/model/useListModel.d.ts +1 -1
  54. package/dist/types/src/model/useListModel.d.ts.map +1 -1
  55. package/dist/types/src/stories/baseline.stories.d.ts +1 -1
  56. package/dist/types/src/stories/baseline.stories.d.ts.map +1 -1
  57. package/dist/types/src/stories/bridge.stories.d.ts +1 -1
  58. package/dist/types/src/stories/bridge.stories.d.ts.map +1 -1
  59. package/dist/types/src/stories/construction.stories.d.ts.map +1 -1
  60. package/dist/types/src/stories/fill.stories.d.ts +1 -1
  61. package/dist/types/src/stories/fill.stories.d.ts.map +1 -1
  62. package/dist/types/src/stories/navigation.stories.d.ts +1 -1
  63. package/dist/types/src/stories/navigation.stories.d.ts.map +1 -1
  64. package/dist/types/src/stories/streaming.stories.d.ts +1 -1
  65. package/dist/types/src/stories/streaming.stories.d.ts.map +1 -1
  66. package/dist/types/src/stories/tail.stories.d.ts +1 -1
  67. package/dist/types/src/stories/tail.stories.d.ts.map +1 -1
  68. package/dist/types/src/stories/widget.stories.d.ts +1 -1
  69. package/dist/types/src/stories/widget.stories.d.ts.map +1 -1
  70. package/dist/types/src/testing/FeedStory.d.ts +1 -1
  71. package/dist/types/src/testing/FeedStory.d.ts.map +1 -1
  72. package/dist/types/src/testing/MessageWindow.d.ts +2 -2
  73. package/dist/types/src/testing/MessageWindow.d.ts.map +1 -1
  74. package/dist/types/src/testing/debug/FeedStats.d.ts +3 -3
  75. package/dist/types/src/testing/debug/FeedStats.d.ts.map +1 -1
  76. package/dist/types/src/testing/debug/index.d.ts +3 -3
  77. package/dist/types/src/testing/debug/index.d.ts.map +1 -1
  78. package/dist/types/src/testing/debug/use-feed-debug.d.ts +1 -1
  79. package/dist/types/src/testing/debug/use-feed-debug.d.ts.map +1 -1
  80. package/dist/types/src/testing/index.d.ts +9 -9
  81. package/dist/types/src/testing/index.d.ts.map +1 -1
  82. package/dist/types/src/testing/scenarios.d.ts +1 -1
  83. package/dist/types/src/testing/scenarios.d.ts.map +1 -1
  84. package/dist/types/src/testing/turn.d.ts +8 -2
  85. package/dist/types/src/testing/turn.d.ts.map +1 -1
  86. package/dist/types/src/testing/widgets.d.ts.map +1 -1
  87. package/dist/types/tsconfig.tsbuildinfo +1 -1
  88. package/package.json +16 -16
  89. package/src/components/Block/MarkdownBlock.tsx +36 -17
  90. package/src/components/Block/block.stories.tsx +4 -4
  91. package/src/components/Block/diff-range.test.ts +33 -0
  92. package/src/components/Block/diff-range.ts +29 -0
  93. package/src/components/Block/extensions.ts +35 -6
  94. package/src/components/Block/index.ts +5 -5
  95. package/src/components/MessageList/MessageList.stories.tsx +1 -1
  96. package/src/components/MessageList/MessageList.tsx +215 -115
  97. package/src/components/MessageList/index.ts +2 -2
  98. package/src/components/Minimap/Minimap.stories.tsx +2 -2
  99. package/src/components/Minimap/Minimap.tsx +4 -4
  100. package/src/components/Minimap/index.ts +1 -1
  101. package/src/components/Outline/Outline.stories.tsx +47 -4
  102. package/src/components/Outline/Outline.tsx +19 -17
  103. package/src/components/Outline/index.ts +1 -1
  104. package/src/components/index.ts +4 -4
  105. package/src/debug/Debug.tsx +6 -6
  106. package/src/debug/Stats.tsx +4 -4
  107. package/src/debug/debug-model.test.ts +1 -1
  108. package/src/debug/frame-meter.test.ts +1 -1
  109. package/src/debug/index.ts +5 -5
  110. package/src/debug/useDebug.tsx +1 -1
  111. package/src/hooks/index.ts +3 -3
  112. package/src/hooks/useFeedNavigation.ts +1 -1
  113. package/src/index.ts +3 -3
  114. package/src/model/feed-model.test.ts +1 -1
  115. package/src/model/feed.test.ts +18 -1
  116. package/src/model/feed.ts +8 -1
  117. package/src/model/index.ts +3 -3
  118. package/src/model/useListModel.ts +1 -1
  119. package/src/stories/baseline.stories.tsx +1 -1
  120. package/src/stories/bridge.stories.tsx +6 -5
  121. package/src/stories/construction.stories.tsx +7 -3
  122. package/src/stories/fill.stories.tsx +1 -1
  123. package/src/stories/horizontal.stories.tsx +2 -2
  124. package/src/stories/mount.stories.tsx +5 -5
  125. package/src/stories/navigation.stories.tsx +1 -1
  126. package/src/stories/streaming.stories.tsx +1 -1
  127. package/src/stories/tail.stories.tsx +1 -1
  128. package/src/stories/widget.stories.tsx +1 -1
  129. package/src/testing/FeedStory.tsx +50 -34
  130. package/src/testing/MessageWindow.tsx +8 -10
  131. package/src/testing/controls.tsx +1 -1
  132. package/src/testing/debug/FeedStats.tsx +11 -10
  133. package/src/testing/debug/index.ts +3 -3
  134. package/src/testing/debug/use-feed-debug.ts +2 -2
  135. package/src/testing/index.ts +9 -9
  136. package/src/testing/scenarios.tsx +27 -33
  137. package/src/testing/turn.ts +54 -30
  138. package/src/testing/widgets.tsx +21 -20
@@ -4,11 +4,15 @@ import { EditorState, StateEffect, StateField } from "@codemirror/state";
4
4
  import { Decoration, EditorView } from "@codemirror/view";
5
5
  import DOMPurify from "dompurify";
6
6
  import { createPortal } from "react-dom";
7
- import { Column, IconButton, Popover, ScrollArea, composable, composableProps, setRef, useThemeContext } from "@dxos/react-ui";
8
- import { createBasicExtensions, createMarkdownExtensions, createThemeExtensions, decorateMarkdown, extendedMarkdown, xmlBlockDecoration, xmlFormatting, xmlTags } from "@dxos/ui-editor";
9
- import { createContext as createContext$1 } from "@radix-ui/react-context";
10
- import { ListModel, useFollow, useWindow } from "@dxos/react-ui-virtual";
7
+ import * as Hooks from "@dxos/react-ui/Hooks";
11
8
  import { mx } from "@dxos/ui-theme";
9
+ import { createBasicExtensions, createMarkdownExtensions, createThemeExtensions, decorateMarkdown, extendedMarkdown, objectLinks, widgetHost, xmlBlockDecoration, xmlFormatting, xmlTags } from "@dxos/ui-editor";
10
+ import { ListModel, useFollow, useWindow, windowRowProps } from "@dxos/react-ui-virtual";
11
+ import * as Button from "@dxos/react-ui/Button";
12
+ import * as Layout from "@dxos/react-ui/Layout";
13
+ import * as ScrollArea from "@dxos/react-ui/ScrollArea";
14
+ import * as Util from "@dxos/react-ui/Util";
15
+ import * as Popover from "@dxos/react-ui/Popover";
12
16
  //#region src/hooks/useDecorations.tsx
13
17
  var DecorationsContext = createContext(/* @__PURE__ */ new Map());
14
18
  /**
@@ -255,6 +259,24 @@ var HtmlBlock = memo(({ html }) => {
255
259
  });
256
260
  HtmlBlock.displayName = "HtmlBlock";
257
261
  //#endregion
262
+ //#region src/components/Block/diff-range.ts
263
+ /**
264
+ * The smallest single replacement that turns `before` into `after`: the common prefix and suffix
265
+ * are left alone, so a document whose middle changed keeps the decorations either side of it.
266
+ */
267
+ var diffRange = (before, after) => {
268
+ const limit = Math.min(before.length, after.length);
269
+ let prefix = 0;
270
+ while (prefix < limit && before.charCodeAt(prefix) === after.charCodeAt(prefix)) prefix++;
271
+ let suffix = 0;
272
+ while (suffix < limit - prefix && before.charCodeAt(before.length - 1 - suffix) === after.charCodeAt(after.length - 1 - suffix)) suffix++;
273
+ return {
274
+ from: prefix,
275
+ to: before.length - suffix,
276
+ insert: after.slice(prefix, after.length - suffix)
277
+ };
278
+ };
279
+ //#endregion
258
280
  //#region src/components/Block/extensions.ts
259
281
  /**
260
282
  * The extension set one item's document is built from.
@@ -269,11 +291,14 @@ HtmlBlock.displayName = "HtmlBlock";
269
291
  * Extensions are descriptors, not instances: a `StateField` or `ViewPlugin` in a shared array is
270
292
  * still instantiated per view, so sharing changes nothing but the identity CodeMirror dedupes on.
271
293
  */
272
- var createBlockExtensions = ({ registry, editable = false, themeMode = "light", setWidgets } = {}) => [...sharedExtensions(registry, editable, themeMode), ...registry ? [xmlTags({
273
- registry,
274
- setWidgets: setWidgets ?? (() => {}),
275
- bookmarks: ["prompt"]
276
- })] : []];
294
+ var createBlockExtensions = ({ registry, objectImage, editable = false, themeMode = "light", setWidgets } = {}) => [...sharedExtensions(registry, editable, themeMode), ...registry ? [
295
+ widgetHost({
296
+ setWidgets,
297
+ bookmarks: ["prompt"]
298
+ }),
299
+ xmlTags({ registry }),
300
+ objectLinks({ image: objectImage })
301
+ ] : []];
277
302
  /** Registries are compared by identity, so a feed's single registry is a single cache scope. */
278
303
  var NO_REGISTRY = {};
279
304
  var cache = /* @__PURE__ */ new WeakMap();
@@ -298,17 +323,32 @@ var build = (registry, editable, themeMode) => [
298
323
  editable,
299
324
  lineWrapping: true
300
325
  }),
301
- createThemeExtensions({ themeMode }),
326
+ createThemeExtensions({
327
+ themeMode,
328
+ syntaxHighlighting: true
329
+ }),
302
330
  registry ? extendedMarkdown({ registry }) : createMarkdownExtensions(),
303
- registry && xmlFormatting({ skip: ["prompt"] }),
331
+ registry && xmlFormatting({ skip: registry.prompt ? ["prompt"] : [] }),
304
332
  decorateMarkdown(),
305
333
  registry?.prompt && xmlBlockDecoration({
306
334
  tag: "prompt",
307
335
  hideTags: true
308
336
  }),
309
337
  highlights,
310
- highlightTheme
338
+ highlightTheme,
339
+ blockScrollerTheme
311
340
  ].filter(Boolean);
341
+ /**
342
+ * An item's editor never scrolls on the block axis: it is auto-height and the feed around it is
343
+ * what scrolls, so its content fits by construction.
344
+ *
345
+ * Left to `auto`, that invariant is broken transiently by any widget that changes height — a tool
346
+ * block's disclosure animating open grows the content every frame while `requestMeasure` lands the
347
+ * editor's new height a frame later, so the scroller is a frame short throughout and paints a
348
+ * scrollbar for the length of the animation. The inline axis keeps `auto`; a wide payload still
349
+ * scrolls.
350
+ */
351
+ var blockScrollerTheme = EditorView.theme({ ".cm-scroller": { overflowY: "hidden" } });
312
352
  //#endregion
313
353
  //#region src/components/Block/MarkdownBlock.tsx
314
354
  /**
@@ -317,8 +357,8 @@ var build = (registry, editable, themeMode) => [
317
357
  * Each item owns a document, so streaming appends and per-message editing stay local — where a
318
358
  * single thread-wide document needs a cursor and a range table to know which message it is touching.
319
359
  */
320
- var MarkdownBlock = memo(({ text, stream, editable = false, registry, hits, onWidgetsChange }) => {
321
- const { themeMode } = useThemeContext();
360
+ var MarkdownBlock = memo(({ classNames, text, stream, editable = false, registry, objectImage, hits, onWidgetsChange }) => {
361
+ const themeMode = Hooks.useThemeMode();
322
362
  const [view, setView] = useState(null);
323
363
  const [widgets, setWidgets] = useState([]);
324
364
  const rootRef = useRef(null);
@@ -327,6 +367,7 @@ var MarkdownBlock = memo(({ text, stream, editable = false, registry, hits, onWi
327
367
  selectionGroupRef.current = selectionGroup;
328
368
  const extensions = useMemo(() => [...createBlockExtensions({
329
369
  registry,
370
+ objectImage,
330
371
  editable,
331
372
  themeMode,
332
373
  setWidgets
@@ -335,19 +376,18 @@ var MarkdownBlock = memo(({ text, stream, editable = false, registry, hits, onWi
335
376
  })], [
336
377
  editable,
337
378
  themeMode,
338
- registry
379
+ registry,
380
+ objectImage
339
381
  ]);
340
382
  const initialTextRef = useRef(text);
341
383
  useLayoutEffect(() => {
342
384
  const parent = rootRef.current;
343
385
  if (!parent) return;
344
- const instance = new EditorView({
345
- parent,
346
- state: EditorState.create({
347
- doc: initialTextRef.current,
348
- extensions
349
- })
350
- });
386
+ const instance = new EditorView({ state: EditorState.create({
387
+ doc: initialTextRef.current,
388
+ extensions
389
+ }) });
390
+ parent.appendChild(instance.dom);
351
391
  const unregister = selectionGroupRef.current.register(instance);
352
392
  setView(instance);
353
393
  return () => {
@@ -368,11 +408,7 @@ var MarkdownBlock = memo(({ text, stream, editable = false, registry, hits, onWi
368
408
  cancelAnimationFrame(dripRaf.current);
369
409
  dripRaf.current = 0;
370
410
  dispatchedRef.current = text;
371
- view.dispatch({ changes: {
372
- from: 0,
373
- to: view.state.doc.length,
374
- insert: text
375
- } });
411
+ view.dispatch({ changes: diffRange(view.state.doc.toString(), text) });
376
412
  return;
377
413
  }
378
414
  if (!stream) {
@@ -440,7 +476,10 @@ var MarkdownBlock = memo(({ text, stream, editable = false, registry, hits, onWi
440
476
  for (const { root } of widgets) observer.observe(root);
441
477
  return () => observer.disconnect();
442
478
  }, [view, widgets]);
443
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", { ref: rootRef }), widgets.map(({ Component, root, id, props }) => /* @__PURE__ */ jsx("div", { children: createPortal(/* @__PURE__ */ jsx(Component, {
479
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
480
+ className: mx(classNames),
481
+ ref: rootRef
482
+ }), widgets.map(({ Component, root, id, props }) => /* @__PURE__ */ jsx("div", { children: createPortal(/* @__PURE__ */ jsx(Component, {
444
483
  view: view ?? void 0,
445
484
  ...props
446
485
  }), root) }, id))] });
@@ -833,16 +872,20 @@ var usePositionLog = () => {
833
872
  };
834
873
  //#endregion
835
874
  //#region src/components/MessageList/MessageList.tsx
836
- var [MessageListProvider, useMessageListContext] = createContext$1("MessageList");
875
+ var MESSAGE_LIST_NAME = "MessageList";
876
+ var [MessageListProvider, useMessageListContext] = Hooks.createContext(MESSAGE_LIST_NAME);
877
+ var [MessageListItemProvider, useMessageListItemContext] = Hooks.createContext(MESSAGE_LIST_NAME);
837
878
  /**
838
879
  * The list's state and scroll controls, for parts that live outside the viewport — a toolbar's
839
880
  * find-next, a statusbar's range readout.
840
881
  */
841
882
  var useMessageList = (consumerName = "useMessageList") => {
842
- const { range, currentIndex, navigation, mountedRows, mountedWidgets, jumps, shifts, breaks, resetShifts, model, scrollToIndex, scrollToBottom } = useMessageListContext(consumerName);
883
+ const { range, currentIndex, atEnd, following, navigation, mountedRows, mountedWidgets, jumps, shifts, breaks, resetShifts, model, scrollToIndex, scrollToBottom } = useMessageListContext(consumerName);
843
884
  return {
844
885
  range,
845
886
  currentIndex,
887
+ atEnd,
888
+ following,
846
889
  navigation,
847
890
  mountedRows,
848
891
  mountedWidgets,
@@ -864,7 +907,7 @@ var DEFAULT_ESTIMATE = 120;
864
907
  * Renders no DOM of its own, so a toolbar or statusbar that reads its state can sit outside the
865
908
  * scroll container — the scrolling part is `MessageList.Viewport`.
866
909
  */
867
- var MessageListRoot = ({ children, model, renderer = defaultRenderer, registry, Chrome = DefaultChrome, Custom, debug, estimateSize = DEFAULT_ESTIMATE, stickyBottom = false, tailLines = 0, overscan = 8, onRangeChange, controllerRef }) => {
910
+ var MessageListRoot = ({ children, model, renderer = defaultRenderer, registry, objectImage, Chrome = DefaultChrome, Custom, debug, estimateSize = DEFAULT_ESTIMATE, stickyBottom = false, tailLines = 0, overscan = 8, onRangeChange, controllerRef }) => {
868
911
  const widgetStore = useMemo(createWidgetStateStore, []);
869
912
  const [viewport, setViewportState] = useState(null);
870
913
  const scrollerRef = useRef(null);
@@ -935,6 +978,11 @@ var MessageListRoot = ({ children, model, renderer = defaultRenderer, registry,
935
978
  });
936
979
  const changedAtRef = useRef(performance.now());
937
980
  useEffect(() => model.subscribe(() => changedAtRef.current = performance.now()), [model]);
981
+ const revisions = useRef(/* @__PURE__ */ new Map());
982
+ useEffect(() => model.subscribe(({ updated }) => {
983
+ for (const id of updated ?? []) revisions.current.set(id, (revisions.current.get(id) ?? 0) + 1);
984
+ }), [model]);
985
+ const revisionOf = useCallback((id) => revisions.current.get(id) ?? 0, []);
938
986
  const follow = useFollow({
939
987
  scrollerRef,
940
988
  placement,
@@ -1031,14 +1079,14 @@ var MessageListRoot = ({ children, model, renderer = defaultRenderer, registry,
1031
1079
  }, [viewport, navigation]);
1032
1080
  const selectionGroup = useMemo(createSelectionGroup, []);
1033
1081
  useEffect(() => {
1034
- setRef(controllerRef, {
1082
+ Hooks.setRef(controllerRef, {
1035
1083
  model,
1036
1084
  scrollToBottom,
1037
1085
  scrollToIndex,
1038
1086
  navigation
1039
1087
  });
1040
1088
  return () => {
1041
- setRef(controllerRef, null);
1089
+ Hooks.setRef(controllerRef, null);
1042
1090
  };
1043
1091
  }, [
1044
1092
  controllerRef,
@@ -1055,11 +1103,14 @@ var MessageListRoot = ({ children, model, renderer = defaultRenderer, registry,
1055
1103
  model,
1056
1104
  renderer,
1057
1105
  registry,
1106
+ objectImage,
1058
1107
  Chrome,
1059
1108
  Custom,
1060
1109
  debug,
1061
1110
  range,
1062
1111
  currentIndex,
1112
+ atEnd: follow.atEnd,
1113
+ following: follow.following,
1063
1114
  navigation,
1064
1115
  mountedRows: mounted,
1065
1116
  mountedWidgets,
@@ -1074,9 +1125,19 @@ var MessageListRoot = ({ children, model, renderer = defaultRenderer, registry,
1074
1125
  first,
1075
1126
  last,
1076
1127
  setViewport,
1128
+ revisionOf,
1077
1129
  scrollToIndex,
1078
1130
  scrollToBottom,
1079
- children
1131
+ children: /* @__PURE__ */ jsx(MessageListItemProvider, {
1132
+ model,
1133
+ renderer,
1134
+ registry,
1135
+ objectImage,
1136
+ Custom,
1137
+ debug,
1138
+ reportWidgets,
1139
+ children
1140
+ })
1080
1141
  })
1081
1142
  })
1082
1143
  });
@@ -1096,43 +1157,38 @@ var MESSAGE_LIST_VIEWPORT_NAME = "MessageList.Viewport";
1096
1157
  * with a toolbar.
1097
1158
  */
1098
1159
  var isEmptyContent = (content, hasCustomRenderer) => content.kind === "markdown" && !content.text.trim() || content.kind === "html" && !content.html.trim() || content.kind === "custom" && !hasCustomRenderer;
1099
- var MessageListViewport = composable(({ autoHide, centered, native, padding, scrollbars, thin, gutter = "md", ...props }, forwardedRef) => {
1100
- const { model, renderer, Chrome, Custom, windowRef, offset, sizerExtent, first, last, setViewport } = useMessageListContext(MESSAGE_LIST_VIEWPORT_NAME);
1160
+ var MessageListViewport = Util.composable(({ autoHide, native, scrollbars, gutter = "md", overlay, ...props }, forwardedRef) => {
1161
+ const { model, renderer, Chrome, Custom, windowRef, offset, sizerExtent, first, last, setViewport, revisionOf } = useMessageListContext(MESSAGE_LIST_VIEWPORT_NAME);
1101
1162
  const { selectedIds, onSelect } = useItemSelectionValue();
1102
1163
  const handleViewportRef = useCallback((element) => {
1103
1164
  setViewport(element);
1104
- setRef(forwardedRef, element);
1165
+ Hooks.setRef(forwardedRef, element);
1105
1166
  }, [setViewport, forwardedRef]);
1167
+ const onSelectRef = useRef(onSelect);
1168
+ onSelectRef.current = onSelect;
1169
+ const handleSelect = useCallback((id, additive) => onSelectRef.current?.(id, additive), []);
1106
1170
  const rows = [];
1107
1171
  for (let index = first; index <= last; index++) {
1108
1172
  const message = model.at(index);
1109
1173
  if (!message) continue;
1110
- const empty = isEmptyContent(renderer(message), Boolean(Custom));
1111
- rows.push(/* @__PURE__ */ jsx("div", {
1112
- "data-index": index,
1113
- "data-object-id": message.id,
1114
- children: !empty && /* @__PURE__ */ jsx(Column.Root, {
1115
- gutter,
1116
- children: /* @__PURE__ */ jsx(Column.Center, { children: /* @__PURE__ */ jsx(Chrome, {
1117
- message,
1118
- index,
1119
- selected: selectedIds?.has(message.id) ?? false,
1120
- onSelect: (id, additive) => onSelect?.(id, additive),
1121
- children: /* @__PURE__ */ jsx(MessageListItem, { message })
1122
- }) })
1123
- })
1174
+ rows.push(/* @__PURE__ */ jsx(MessageListRow, {
1175
+ message,
1176
+ revision: revisionOf(message.id),
1177
+ index,
1178
+ empty: isEmptyContent(renderer(message), Boolean(Custom)),
1179
+ selected: selectedIds?.has(message.id) ?? false,
1180
+ gutter,
1181
+ Chrome,
1182
+ onSelect: handleSelect
1124
1183
  }, message.id));
1125
1184
  }
1126
- return /* @__PURE__ */ jsx(ScrollArea.Root, {
1127
- ...composableProps(props),
1185
+ return /* @__PURE__ */ jsxs(ScrollArea.Root, {
1186
+ ...Util.composableProps(props),
1128
1187
  orientation: "vertical",
1129
1188
  autoHide,
1130
- centered,
1131
1189
  native,
1132
- padding,
1133
1190
  scrollbars,
1134
- thin,
1135
- children: /* @__PURE__ */ jsxs(ScrollArea.Viewport, {
1191
+ children: [/* @__PURE__ */ jsxs(ScrollArea.Viewport, {
1136
1192
  "data-testid": "feed.viewport",
1137
1193
  style: {
1138
1194
  position: "relative",
@@ -1150,23 +1206,45 @@ var MessageListViewport = composable(({ autoHide, centered, native, padding, scr
1150
1206
  "data-testid": "feed.window",
1151
1207
  children: rows
1152
1208
  })]
1153
- })
1209
+ }), overlay]
1154
1210
  });
1155
1211
  });
1156
1212
  MessageListViewport.displayName = MESSAGE_LIST_VIEWPORT_NAME;
1213
+ /**
1214
+ * One mounted row. Memoized because the window re-renders on every scroll event and after every
1215
+ * measurement, and nothing in a row depends on where the window is — only on its own props.
1216
+ */
1217
+ var MessageListRow = memo(({ message, index, empty, selected, gutter, Chrome, onSelect }) => /* @__PURE__ */ jsx("div", {
1218
+ "data-object-id": message.id,
1219
+ ...windowRowProps(index, message.id),
1220
+ children: !empty && /* @__PURE__ */ jsx(Layout.Container, {
1221
+ gutter,
1222
+ children: /* @__PURE__ */ jsx("div", {
1223
+ className: "dx-container-type-inline-size",
1224
+ children: /* @__PURE__ */ jsx(Chrome, {
1225
+ message,
1226
+ index,
1227
+ selected,
1228
+ onSelect,
1229
+ children: /* @__PURE__ */ jsx(MessageListItem, { message })
1230
+ })
1231
+ })
1232
+ })
1233
+ }));
1234
+ MessageListRow.displayName = "MessageList.Row";
1157
1235
  var MESSAGE_LIST_ITEM_NAME = "MessageList.Item";
1158
1236
  /**
1159
1237
  * One message, rendered by the kind its renderer resolves. Exposed so a host can render a message
1160
1238
  * outside the scrolling window — a pinned message, a preview — through the same path.
1161
1239
  */
1162
- var MessageListItem = composable(({ message, ...props }, forwardedRef) => {
1163
- const { model, renderer, registry, Custom, debug, reportWidgets } = useMessageListContext(MESSAGE_LIST_ITEM_NAME);
1240
+ var MessageListItem = Util.composable(({ message, ...props }, forwardedRef) => {
1241
+ const { model, renderer, registry, objectImage, Custom, debug, reportWidgets } = useMessageListItemContext(MESSAGE_LIST_ITEM_NAME);
1164
1242
  const content = renderer(message);
1165
1243
  const decorations = useDecorations(message.id);
1166
1244
  const hits = useMemo(() => decorations.length ? decorations.map(({ range }) => [range.offset, range.offset + range.length]) : void 0, [decorations]);
1167
1245
  const handleWidgetsChange = useCallback((count) => reportWidgets(message.id, count), [reportWidgets, message.id]);
1168
1246
  return /* @__PURE__ */ jsxs("div", {
1169
- ...composableProps(props, { classNames: debug ? "outline outline-1 outline-dashed outline-primary-500/50 [&_.cm-content>*]:outline [&_.cm-content>*]:outline-1 [&_.cm-content>*]:outline-dashed [&_.cm-content>*]:outline-neutral-500/40" : void 0 }),
1247
+ ...Util.composableProps(props, { classNames: debug ? "outline outline-1 outline-dashed outline-primary-500/50 [&_.cm-content>*]:outline [&_.cm-content>*]:outline-1 [&_.cm-content>*]:outline-dashed [&_.cm-content>*]:outline-neutral-500/40" : void 0 }),
1170
1248
  ref: forwardedRef,
1171
1249
  children: [
1172
1250
  content.kind === "markdown" && /* @__PURE__ */ jsx(WidgetScopeProvider, {
@@ -1175,6 +1253,7 @@ var MessageListItem = composable(({ message, ...props }, forwardedRef) => {
1175
1253
  text: content.text,
1176
1254
  stream: model.streamingId === message.id,
1177
1255
  registry,
1256
+ objectImage,
1178
1257
  hits,
1179
1258
  onWidgetsChange: handleWidgetsChange
1180
1259
  })
@@ -1198,7 +1277,7 @@ var MESSAGE_LIST_NAV_NAME = "MessageList.Nav";
1198
1277
  * the same keymap the viewport itself carries, so focus landing on the toolbar does not change
1199
1278
  * what the keys mean.
1200
1279
  */
1201
- var MessageListNav = composable(({ ends = true, ...props }, forwardedRef) => {
1280
+ var MessageListNav = Util.composable(({ ends = true, ...props }, forwardedRef) => {
1202
1281
  const { navigation } = useMessageListContext(MESSAGE_LIST_NAV_NAME);
1203
1282
  const onKeyDown = useCallback((event) => {
1204
1283
  const delta = event.key === "ArrowDown" ? 1 : event.key === "ArrowUp" ? -1 : 0;
@@ -1213,11 +1292,11 @@ var MessageListNav = composable(({ ends = true, ...props }, forwardedRef) => {
1213
1292
  }, [navigation]);
1214
1293
  return /* @__PURE__ */ jsxs("div", {
1215
1294
  role: "group",
1216
- ...composableProps(props),
1295
+ ...Util.composableProps(props),
1217
1296
  onKeyDown,
1218
1297
  ref: forwardedRef,
1219
1298
  children: [
1220
- ends && /* @__PURE__ */ jsx(IconButton, {
1299
+ ends && /* @__PURE__ */ jsx(Button.Root, {
1221
1300
  icon: "ph--arrow-line-up--regular",
1222
1301
  iconOnly: true,
1223
1302
  label: "First message",
@@ -1225,7 +1304,7 @@ var MessageListNav = composable(({ ends = true, ...props }, forwardedRef) => {
1225
1304
  "data-testid": "feed.nav.top",
1226
1305
  onClick: () => navigation.first()
1227
1306
  }),
1228
- /* @__PURE__ */ jsx(IconButton, {
1307
+ /* @__PURE__ */ jsx(Button.Root, {
1229
1308
  icon: "ph--caret-up--regular",
1230
1309
  iconOnly: true,
1231
1310
  label: "Previous message",
@@ -1233,7 +1312,7 @@ var MessageListNav = composable(({ ends = true, ...props }, forwardedRef) => {
1233
1312
  "data-testid": "feed.nav.back",
1234
1313
  onClick: () => navigation.step(-1)
1235
1314
  }),
1236
- /* @__PURE__ */ jsx(IconButton, {
1315
+ /* @__PURE__ */ jsx(Button.Root, {
1237
1316
  icon: "ph--caret-down--regular",
1238
1317
  iconOnly: true,
1239
1318
  label: "Next message",
@@ -1241,7 +1320,7 @@ var MessageListNav = composable(({ ends = true, ...props }, forwardedRef) => {
1241
1320
  "data-testid": "feed.nav.forward",
1242
1321
  onClick: () => navigation.step(1)
1243
1322
  }),
1244
- ends && /* @__PURE__ */ jsx(IconButton, {
1323
+ ends && /* @__PURE__ */ jsx(Button.Root, {
1245
1324
  icon: "ph--arrow-line-down--regular",
1246
1325
  iconOnly: true,
1247
1326
  label: "Last message",
@@ -1277,7 +1356,7 @@ var Minimap = ({ classNames, state, width = DEFAULT_WIDTH$1, onSelect }) => {
1277
1356
  }),
1278
1357
  children: state && total > 0 && /* @__PURE__ */ jsxs(Fragment$1, { children: [
1279
1358
  /* @__PURE__ */ jsx("div", {
1280
- className: "absolute inset-x-0 bg-accent-fill/30",
1359
+ className: "absolute inset-x-0 bg-accent-bg/30",
1281
1360
  style: {
1282
1361
  top: scale(state.geometry.window.start),
1283
1362
  height: scale(state.geometry.window.extent)
@@ -1289,7 +1368,7 @@ var Minimap = ({ classNames, state, width = DEFAULT_WIDTH$1, onSelect }) => {
1289
1368
  style: { top: `${(step + 1) / 10 * 100}%` }
1290
1369
  }, step)),
1291
1370
  /* @__PURE__ */ jsx("div", {
1292
- className: "absolute inset-x-0 border-y border-accent-bg bg-accent-fill/70",
1371
+ className: "absolute inset-x-0 border-y border-accent-bg bg-accent-bg/70",
1293
1372
  style: {
1294
1373
  top: scale(state.geometry.scroll),
1295
1374
  height: scale(state.geometry.viewport)
@@ -1404,6 +1483,12 @@ var Outline = ({ classNames, markers, visibleRange, tickSize = TICK_SIZE, width
1404
1483
  const hoveredMarker = shown == null ? void 0 : rows[shown]?.marker;
1405
1484
  return /* @__PURE__ */ jsxs(Popover.Root, {
1406
1485
  open: hoveredMarker != null,
1486
+ positioning: {
1487
+ placement: "right",
1488
+ flip: false,
1489
+ slide: false
1490
+ },
1491
+ autoFocus: false,
1407
1492
  children: [/* @__PURE__ */ jsxs("div", {
1408
1493
  role: "navigation",
1409
1494
  className: mx("relative flex flex-col justify-center overflow-hidden", classNames),
@@ -1454,21 +1539,19 @@ var Outline = ({ classNames, markers, visibleRange, tickSize = TICK_SIZE, width
1454
1539
  }
1455
1540
  })
1456
1541
  })]
1457
- }), hoveredMarker && /* @__PURE__ */ jsxs(Popover.Content, {
1458
- side: "right",
1459
- align: "center",
1460
- avoidCollisions: false,
1461
- onOpenAutoFocus: (event) => event.preventDefault(),
1462
- children: [/* @__PURE__ */ jsx(Popover.Viewport, { children: /* @__PURE__ */ jsxs("div", {
1463
- className: "px-2 py-1 max-w-[24rem] w-[24rem]",
1542
+ }), hoveredMarker && /* @__PURE__ */ jsx(Popover.Content, {
1543
+ classNames: "w-[32rem] max-w-(--available-width)",
1544
+ children: /* @__PURE__ */ jsxs(Layout.Flex, {
1545
+ column: true,
1546
+ classNames: "gap-1 px-2 py-1",
1464
1547
  children: [/* @__PURE__ */ jsx("p", {
1465
- className: "truncate font-medium",
1548
+ className: "font-medium line-clamp-2",
1466
1549
  children: hoveredMarker.title
1467
1550
  }), hoveredMarker.description && /* @__PURE__ */ jsx("p", {
1468
- className: "mt-1 text-sm text-description line-clamp-3",
1551
+ className: "text-sm text-fg-muted line-clamp-4",
1469
1552
  children: hoveredMarker.description
1470
1553
  })]
1471
- }) }), /* @__PURE__ */ jsx(Popover.Arrow, {})]
1554
+ })
1472
1555
  }, hoveredMarker.id)]
1473
1556
  });
1474
1557
  };