@elliemae/ds-chat-container 3.80.0-next.4 → 3.80.0-next.41

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/dist/cjs/DSChatContainerDefinitions.js +7 -1
  2. package/dist/cjs/DSChatContainerDefinitions.js.map +2 -2
  3. package/dist/cjs/parts/chat-container-messages-list/ChatContainerMessagesList.js +134 -69
  4. package/dist/cjs/parts/chat-container-messages-list/ChatContainerMessagesList.js.map +2 -2
  5. package/dist/cjs/parts/chat-container-messages-list/styled.js +33 -35
  6. package/dist/cjs/parts/chat-container-messages-list/styled.js.map +2 -2
  7. package/dist/cjs/parts/chat-container-messages-list/useActionRef.js +2 -5
  8. package/dist/cjs/parts/chat-container-messages-list/useActionRef.js.map +2 -2
  9. package/dist/cjs/parts/chat-container-messages-list/useChatContainerMessageList.js +33 -20
  10. package/dist/cjs/parts/chat-container-messages-list/useChatContainerMessageList.js.map +2 -2
  11. package/dist/cjs/parts/chat-container-messages-list/useFocusAndScrollHelpers.js +38 -43
  12. package/dist/cjs/parts/chat-container-messages-list/useFocusAndScrollHelpers.js.map +2 -2
  13. package/dist/cjs/parts/chat-container-messages-list/useGlobalEventListener.js.map +1 -1
  14. package/dist/cjs/parts/chat-container-messages-list/useKeyboard.js +8 -15
  15. package/dist/cjs/parts/chat-container-messages-list/useKeyboard.js.map +2 -2
  16. package/dist/cjs/parts/chat-container-messages-list/useMessageFocusTracker.js +132 -0
  17. package/dist/cjs/parts/chat-container-messages-list/useMessageFocusTracker.js.map +7 -0
  18. package/dist/cjs/react-desc-prop-types.js +39 -23
  19. package/dist/cjs/react-desc-prop-types.js.map +2 -2
  20. package/dist/cjs/typescript-testing/typescript-chat-container-valid.js +31 -0
  21. package/dist/cjs/typescript-testing/typescript-chat-container-valid.js.map +2 -2
  22. package/dist/esm/DSChatContainerDefinitions.js +7 -1
  23. package/dist/esm/DSChatContainerDefinitions.js.map +2 -2
  24. package/dist/esm/parts/chat-container-messages-list/ChatContainerMessagesList.js +135 -72
  25. package/dist/esm/parts/chat-container-messages-list/ChatContainerMessagesList.js.map +2 -2
  26. package/dist/esm/parts/chat-container-messages-list/styled.js +33 -35
  27. package/dist/esm/parts/chat-container-messages-list/styled.js.map +2 -2
  28. package/dist/esm/parts/chat-container-messages-list/useActionRef.js +2 -5
  29. package/dist/esm/parts/chat-container-messages-list/useActionRef.js.map +2 -2
  30. package/dist/esm/parts/chat-container-messages-list/useChatContainerMessageList.js +33 -20
  31. package/dist/esm/parts/chat-container-messages-list/useChatContainerMessageList.js.map +2 -2
  32. package/dist/esm/parts/chat-container-messages-list/useFocusAndScrollHelpers.js +39 -44
  33. package/dist/esm/parts/chat-container-messages-list/useFocusAndScrollHelpers.js.map +2 -2
  34. package/dist/esm/parts/chat-container-messages-list/useGlobalEventListener.js.map +1 -1
  35. package/dist/esm/parts/chat-container-messages-list/useKeyboard.js +8 -15
  36. package/dist/esm/parts/chat-container-messages-list/useKeyboard.js.map +2 -2
  37. package/dist/esm/parts/chat-container-messages-list/useMessageFocusTracker.js +102 -0
  38. package/dist/esm/parts/chat-container-messages-list/useMessageFocusTracker.js.map +7 -0
  39. package/dist/esm/react-desc-prop-types.js +40 -24
  40. package/dist/esm/react-desc-prop-types.js.map +2 -2
  41. package/dist/esm/typescript-testing/typescript-chat-container-valid.js +31 -0
  42. package/dist/esm/typescript-testing/typescript-chat-container-valid.js.map +2 -2
  43. package/dist/types/DSChatContainerDefinitions.d.ts +8 -0
  44. package/dist/types/parts/chat-container-messages-list/styled.d.ts +18 -5
  45. package/dist/types/parts/chat-container-messages-list/useActionRef.d.ts +4 -6
  46. package/dist/types/parts/chat-container-messages-list/useChatContainerMessageList.d.ts +7 -3
  47. package/dist/types/parts/chat-container-messages-list/useFocusAndScrollHelpers.d.ts +9 -6
  48. package/dist/types/parts/chat-container-messages-list/useGlobalEventListener.d.ts +1 -1
  49. package/dist/types/parts/chat-container-messages-list/useKeyboard.d.ts +7 -3
  50. package/dist/types/parts/chat-container-messages-list/useMessageFocusTracker.d.ts +52 -0
  51. package/dist/types/react-desc-prop-types.d.ts +800 -789
  52. package/package.json +17 -17
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/parts/chat-container-messages-list/useChatContainerMessageList.ts", "../../../../../../../scripts/build/transpile/react-shim.js"],
4
- "sourcesContent": ["/* eslint-disable complexity */\n/* eslint-disable jsx-a11y/no-noninteractive-tabindex */\n/* eslint-disable max-lines */\nimport React from 'react';\nimport {\n useMemoMergePropsWithDefault,\n useGetGlobalAttributes,\n useValidateTypescriptPropTypes,\n} from '@elliemae/ds-props-helpers';\nimport { useVirtual } from 'react-virtual';\nimport { useActionRef } from './useActionRef.js';\nimport { useGlobalEventListener } from './useGlobalEventListener.js';\nimport { useKeyboard } from './useKeyboard.js';\nimport { useFocusAndScrollHelpers } from './useFocusAndScrollHelpers.js';\nimport type { DSChatT } from '../../react-desc-prop-types.js';\nimport { ChatContainerMessagesListPropTypes, defaultPropsMessagesList } from '../../react-desc-prop-types.js';\nimport { DSChatContainerMessagesList } from '../../DSChatContainerDefinitions.js';\n\nconst getPadding = ({ hasMoreItems, bannerPosition, isLoading }: DSChatT.GetPaddingT) => {\n const padding = {\n paddingStart: 0,\n paddingEnd: -5,\n };\n if ([hasMoreItems, bannerPosition, isLoading].some((item) => item === 'top')) padding.paddingStart = 42;\n else if ([hasMoreItems, bannerPosition, isLoading].some((item) => item === 'bottom')) padding.paddingEnd = 42;\n return padding;\n};\n\ntype UseChatContainerMessageList = (props: DSChatT.MessagesListProps) => {\n propsWithDefault: DSChatT.MessagesListProps;\n globalAttributes: ReturnType<typeof useGetGlobalAttributes>;\n useVirtualHelpers: ReturnType<typeof useVirtual>;\n loadMoreRef: React.MutableRefObject<HTMLButtonElement | undefined>;\n virtualListRef: React.MutableRefObject<HTMLDivElement | undefined>;\n} & ReturnType<typeof useKeyboard>;\n\nexport const useChatContainerMessageList: UseChatContainerMessageList = (props) => {\n const propsWithDefault = useMemoMergePropsWithDefault<DSChatT.MessagesListProps>(props, defaultPropsMessagesList);\n useValidateTypescriptPropTypes<DSChatT.MessagesListProps>(\n propsWithDefault,\n ChatContainerMessagesListPropTypes,\n DSChatContainerMessagesList,\n );\n const globalAttributes = useGetGlobalAttributes(propsWithDefault);\n const loadMoreRef = React.useRef<HTMLButtonElement>();\n const virtualListRef = React.useRef<HTMLDivElement>();\n\n const pendingOnScroll = React.useRef(false);\n const { bannerPosition, messages, actionRef, hasMoreItems, isLoading } = propsWithDefault;\n const useVirtualHelpers = useVirtual({\n size: messages?.length,\n parentRef: virtualListRef,\n overscan: 15,\n ...getPadding({ hasMoreItems, bannerPosition, isLoading }),\n // estimateSize should not be really required given what was stated on\n // https://github.com/tannerlinsley/react-virtual/issues/23\n estimateSize: React.useCallback(() => 80, []),\n });\n\n const { onScrollStop, updateCache, getMessagesInView, onlyFocus, scrollAndFocus, checkPendingOnScroll } =\n useFocusAndScrollHelpers({\n propsWithDefault,\n useVirtualHelpers,\n pendingOnScroll,\n virtualListRef,\n });\n\n useActionRef({\n actionRef,\n useVirtualHelpers,\n virtualListRef,\n updateCache,\n getMessagesInView,\n onlyFocus,\n scrollAndFocus,\n });\n\n useGlobalEventListener({\n propsWithDefault,\n virtualListRef,\n onScrollStop,\n checkPendingOnScroll,\n });\n\n const keyboardMethods = useKeyboard({\n propsWithDefault,\n actionRef,\n virtualListRef,\n loadMoreRef,\n scrollAndFocus,\n onlyFocus,\n getMessagesInView,\n });\n\n return React.useMemo(\n () => ({\n propsWithDefault,\n globalAttributes,\n useVirtualHelpers,\n loadMoreRef,\n virtualListRef,\n ...keyboardMethods,\n }),\n [globalAttributes, keyboardMethods, propsWithDefault, useVirtualHelpers, loadMoreRef, virtualListRef],\n );\n};\n", "import * as React from 'react';\nexport { React };\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;ADGvB,mBAAkB;AAClB,8BAIO;AACP,2BAA2B;AAC3B,0BAA6B;AAC7B,oCAAuC;AACvC,yBAA4B;AAC5B,sCAAyC;AAEzC,mCAA6E;AAC7E,wCAA4C;AAE5C,MAAM,aAAa,CAAC,EAAE,cAAc,gBAAgB,UAAU,MAA2B;AACvF,QAAM,UAAU;AAAA,IACd,cAAc;AAAA,IACd,YAAY;AAAA,EACd;AACA,MAAI,CAAC,cAAc,gBAAgB,SAAS,EAAE,KAAK,CAAC,SAAS,SAAS,KAAK,EAAG,SAAQ,eAAe;AAAA,WAC5F,CAAC,cAAc,gBAAgB,SAAS,EAAE,KAAK,CAAC,SAAS,SAAS,QAAQ,EAAG,SAAQ,aAAa;AAC3G,SAAO;AACT;AAUO,MAAM,8BAA2D,CAAC,UAAU;AACjF,QAAM,uBAAmB,sDAAwD,OAAO,qDAAwB;AAChH;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,QAAM,uBAAmB,gDAAuB,gBAAgB;AAChE,QAAM,cAAc,aAAAA,QAAM,OAA0B;AACpD,QAAM,iBAAiB,aAAAA,QAAM,OAAuB;AAEpD,QAAM,kBAAkB,aAAAA,QAAM,OAAO,KAAK;AAC1C,QAAM,EAAE,gBAAgB,UAAU,WAAW,cAAc,UAAU,IAAI;AACzE,QAAM,wBAAoB,iCAAW;AAAA,IACnC,MAAM,UAAU;AAAA,IAChB,WAAW;AAAA,IACX,UAAU;AAAA,IACV,GAAG,WAAW,EAAE,cAAc,gBAAgB,UAAU,CAAC;AAAA;AAAA;AAAA,IAGzD,cAAc,aAAAA,QAAM,YAAY,MAAM,IAAI,CAAC,CAAC;AAAA,EAC9C,CAAC;AAED,QAAM,EAAE,cAAc,aAAa,mBAAmB,WAAW,gBAAgB,qBAAqB,QACpG,0DAAyB;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAEH,wCAAa;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,4DAAuB;AAAA,IACrB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,QAAM,sBAAkB,gCAAY;AAAA,IAClC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,SAAO,aAAAA,QAAM;AAAA,IACX,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,GAAG;AAAA,IACL;AAAA,IACA,CAAC,kBAAkB,iBAAiB,kBAAkB,mBAAmB,aAAa,cAAc;AAAA,EACtG;AACF;",
4
+ "sourcesContent": ["/* eslint-disable complexity */\n/* eslint-disable jsx-a11y/no-noninteractive-tabindex */\n/* eslint-disable max-lines */\nimport React from 'react';\nimport { useTheme } from 'styled-components';\nimport {\n useMemoMergePropsWithDefault,\n useGetGlobalAttributes,\n useValidateTypescriptPropTypes,\n} from '@elliemae/ds-props-helpers';\nimport type { DSFastListT } from '@elliemae/ds-fast-list';\nimport { useActionRef } from './useActionRef.js';\nimport { useGlobalEventListener } from './useGlobalEventListener.js';\nimport { useKeyboard } from './useKeyboard.js';\nimport { useFocusAndScrollHelpers } from './useFocusAndScrollHelpers.js';\nimport { useMessageFocusTracker } from './useMessageFocusTracker.js';\nimport type { DSChatT } from '../../react-desc-prop-types.js';\nimport { ChatContainerMessagesListPropTypes, defaultPropsMessagesList } from '../../react-desc-prop-types.js';\nimport { DSChatContainerMessagesList } from '../../DSChatContainerDefinitions.js';\n\nconst getPadding = ({ hasMoreItems, bannerPosition, isLoading }: DSChatT.GetPaddingT) => {\n const padding = {\n paddingStart: 0,\n paddingEnd: -5,\n };\n if ([hasMoreItems, bannerPosition, isLoading].some((item) => item === 'top')) padding.paddingStart = 42;\n else if ([hasMoreItems, bannerPosition, isLoading].some((item) => item === 'bottom')) padding.paddingEnd = 42;\n return padding;\n};\n\ntype UseChatContainerMessageList = (props: DSChatT.MessagesListProps) => {\n propsWithDefault: DSChatT.MessagesListProps;\n globalAttributes: ReturnType<typeof useGetGlobalAttributes>;\n fastListActionRef: React.MutableRefObject<DSFastListT.ActionRef>;\n paddingStart: number;\n paddingEnd: number;\n loadMoreRef: React.MutableRefObject<HTMLButtonElement | undefined>;\n virtualListRef: React.MutableRefObject<HTMLDivElement | null>;\n focusTracker: ReturnType<typeof useMessageFocusTracker>;\n} & ReturnType<typeof useKeyboard>;\n\nexport const useChatContainerMessageList: UseChatContainerMessageList = (props) => {\n const propsWithDefault = useMemoMergePropsWithDefault<DSChatT.MessagesListProps>(props, defaultPropsMessagesList);\n useValidateTypescriptPropTypes<DSChatT.MessagesListProps>(\n propsWithDefault,\n ChatContainerMessagesListPropTypes,\n DSChatContainerMessagesList,\n );\n const globalAttributes = useGetGlobalAttributes(propsWithDefault);\n const loadMoreRef = React.useRef<HTMLButtonElement>();\n const virtualListRef = React.useRef<HTMLDivElement | null>(null);\n const fastListActionRef = React.useRef<DSFastListT.ActionRef>({});\n\n const pendingOnScroll = React.useRef(false);\n const { bannerPosition, actionRef, hasMoreItems, isLoading } = propsWithDefault;\n const theme = useTheme();\n // The scroll container's own CSS top-padding was removed (see styled.ts's StyledFastList) and\n // replaced with this \u2014 an equivalent virtualizer-space paddingStart keeps getVisibleItems()'s\n // scrollOffset comparisons in the same frame as its item measurements.\n const containerPaddingStart = parseInt(theme.space.xs, 10);\n const padding = getPadding({ hasMoreItems, bannerPosition, isLoading });\n const paddingStart = padding.paddingStart + containerPaddingStart;\n const { paddingEnd } = padding;\n\n const focusTracker = useMessageFocusTracker(virtualListRef);\n const { trackFocusMessage } = focusTracker;\n\n const { vScroll, onScrollStop, getMessagesInView, onlyFocus, scrollAndFocus, checkPendingOnScroll } =\n useFocusAndScrollHelpers({\n propsWithDefault,\n fastListActionRef,\n pendingOnScroll,\n trackFocusMessage,\n });\n\n useActionRef({\n actionRef,\n vScroll,\n virtualListRef,\n getMessagesInView,\n onlyFocus,\n scrollAndFocus,\n });\n\n useGlobalEventListener({\n propsWithDefault,\n virtualListRef,\n onScrollStop,\n checkPendingOnScroll,\n });\n\n const keyboardMethods = useKeyboard({\n propsWithDefault,\n actionRef,\n virtualListRef,\n loadMoreRef,\n scrollAndFocus,\n onlyFocus,\n getMessagesInView,\n trackFocusMessage,\n });\n\n return React.useMemo(\n () => ({\n propsWithDefault,\n globalAttributes,\n fastListActionRef,\n paddingStart,\n paddingEnd,\n loadMoreRef,\n virtualListRef,\n focusTracker,\n ...keyboardMethods,\n }),\n [\n globalAttributes,\n keyboardMethods,\n propsWithDefault,\n fastListActionRef,\n paddingStart,\n paddingEnd,\n loadMoreRef,\n virtualListRef,\n focusTracker,\n ],\n );\n};\n", "import * as React from 'react';\nexport { React };\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;ADGvB,mBAAkB;AAClB,+BAAyB;AACzB,8BAIO;AAEP,0BAA6B;AAC7B,oCAAuC;AACvC,yBAA4B;AAC5B,sCAAyC;AACzC,oCAAuC;AAEvC,mCAA6E;AAC7E,wCAA4C;AAE5C,MAAM,aAAa,CAAC,EAAE,cAAc,gBAAgB,UAAU,MAA2B;AACvF,QAAM,UAAU;AAAA,IACd,cAAc;AAAA,IACd,YAAY;AAAA,EACd;AACA,MAAI,CAAC,cAAc,gBAAgB,SAAS,EAAE,KAAK,CAAC,SAAS,SAAS,KAAK,EAAG,SAAQ,eAAe;AAAA,WAC5F,CAAC,cAAc,gBAAgB,SAAS,EAAE,KAAK,CAAC,SAAS,SAAS,QAAQ,EAAG,SAAQ,aAAa;AAC3G,SAAO;AACT;AAaO,MAAM,8BAA2D,CAAC,UAAU;AACjF,QAAM,uBAAmB,sDAAwD,OAAO,qDAAwB;AAChH;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,QAAM,uBAAmB,gDAAuB,gBAAgB;AAChE,QAAM,cAAc,aAAAA,QAAM,OAA0B;AACpD,QAAM,iBAAiB,aAAAA,QAAM,OAA8B,IAAI;AAC/D,QAAM,oBAAoB,aAAAA,QAAM,OAA8B,CAAC,CAAC;AAEhE,QAAM,kBAAkB,aAAAA,QAAM,OAAO,KAAK;AAC1C,QAAM,EAAE,gBAAgB,WAAW,cAAc,UAAU,IAAI;AAC/D,QAAM,YAAQ,mCAAS;AAIvB,QAAM,wBAAwB,SAAS,MAAM,MAAM,IAAI,EAAE;AACzD,QAAM,UAAU,WAAW,EAAE,cAAc,gBAAgB,UAAU,CAAC;AACtE,QAAM,eAAe,QAAQ,eAAe;AAC5C,QAAM,EAAE,WAAW,IAAI;AAEvB,QAAM,mBAAe,sDAAuB,cAAc;AAC1D,QAAM,EAAE,kBAAkB,IAAI;AAE9B,QAAM,EAAE,SAAS,cAAc,mBAAmB,WAAW,gBAAgB,qBAAqB,QAChG,0DAAyB;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAEH,wCAAa;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,4DAAuB;AAAA,IACrB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,QAAM,sBAAkB,gCAAY;AAAA,IAClC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,SAAO,aAAAA,QAAM;AAAA,IACX,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,GAAG;AAAA,IACL;AAAA,IACA;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;",
6
6
  "names": ["React"]
7
7
  }
@@ -34,41 +34,36 @@ module.exports = __toCommonJS(useFocusAndScrollHelpers_exports);
34
34
  var React = __toESM(require("react"));
35
35
  var import_react = __toESM(require("react"));
36
36
  var import_lodash_es = require("lodash-es");
37
+ var import_useMessageFocusTracker = require("./useMessageFocusTracker.js");
37
38
  const useFocusAndScrollHelpers = ({
38
- propsWithDefault: { onScrollEnds },
39
- useVirtualHelpers: { scrollToIndex: vScroll },
39
+ propsWithDefault: { onScrollEnds, messages },
40
+ fastListActionRef,
40
41
  pendingOnScroll,
41
- virtualListRef
42
+ trackFocusMessage
42
43
  }) => {
44
+ const vScroll = import_react.default.useCallback(
45
+ (index, opts) => {
46
+ const scrollOnce = () => {
47
+ const fastList = fastListActionRef.current;
48
+ const offset = fastList.getOffsetForIndex?.(index, opts?.align ?? "center");
49
+ if (offset !== void 0) fastList.scrollToOffset?.(offset, { behavior: opts?.behavior });
50
+ };
51
+ scrollOnce();
52
+ requestAnimationFrame(scrollOnce);
53
+ },
54
+ [fastListActionRef]
55
+ );
43
56
  const computeVisibleItems = import_react.default.useCallback(() => {
44
57
  if (document.visibilityState === "hidden") {
45
58
  pendingOnScroll.current = true;
46
59
  return [];
47
60
  }
48
- if (!virtualListRef.current) return [];
61
+ const visibleItems = fastListActionRef.current.getVisibleItems?.() ?? [];
62
+ if (visibleItems.length === 0) return [];
49
63
  pendingOnScroll.current = false;
50
- const { bottom, top } = virtualListRef.current.getBoundingClientRect();
51
- const list = virtualListRef.current.querySelectorAll('[data-element="chat-element-list-item"]');
52
- const indexList = [];
53
- list.forEach((item) => {
54
- const { top: itemTop } = item.getBoundingClientRect();
55
- const itemPadding = 10;
56
- if (itemTop + itemPadding < bottom && itemTop + itemPadding > top) indexList.push(item.dataset.index || "");
57
- });
58
- return indexList;
59
- }, [pendingOnScroll, virtualListRef]);
60
- const visibleItemsCache = import_react.default.useRef([]);
61
- const updateCache = import_react.default.useMemo(
62
- () => (0, import_lodash_es.throttle)(
63
- () => {
64
- visibleItemsCache.current = computeVisibleItems();
65
- },
66
- 500,
67
- { leading: true }
68
- ),
69
- [computeVisibleItems]
70
- );
71
- const getMessagesInView = import_react.default.useCallback(() => [...visibleItemsCache.current], []);
64
+ return visibleItems.map(String);
65
+ }, [fastListActionRef, pendingOnScroll]);
66
+ const getMessagesInView = computeVisibleItems;
72
67
  const onScrollStop = import_react.default.useMemo(
73
68
  () => (0, import_lodash_es.debounce)(() => {
74
69
  const indexList = computeVisibleItems();
@@ -79,39 +74,39 @@ const useFocusAndScrollHelpers = ({
79
74
  );
80
75
  const onlyFocus = import_react.default.useCallback(
81
76
  (index) => {
82
- virtualListRef.current?.querySelector(`[data-index="${index}"]`)?.focus();
77
+ const position = Number(index);
78
+ const row = messages?.[position];
79
+ const mounted = fastListActionRef.current.getMountedItems?.() ?? [];
80
+ if (row && mounted.includes(position))
81
+ trackFocusMessage((0, import_useMessageFocusTracker.getMessageFocusId)(row, position), { preventScroll: true });
83
82
  },
84
- [virtualListRef]
83
+ [messages, fastListActionRef, trackFocusMessage]
85
84
  );
86
85
  const scrollAndFocus = import_react.default.useCallback(
87
- (index) => {
88
- vScroll(index, { align: "center" });
86
+ (index, opts = { align: "center" }) => {
87
+ const row = messages?.[index];
88
+ const messageId = row ? (0, import_useMessageFocusTracker.getMessageFocusId)(row, index) : (0, import_useMessageFocusTracker.getMessagePositionId)(index);
89
+ trackFocusMessage(messageId, { preventScroll: true });
90
+ vScroll(index, opts);
89
91
  requestAnimationFrame(() => {
90
- vScroll(index, { align: "center" });
92
+ vScroll(index, opts);
91
93
  });
92
- setTimeout(() => {
93
- onlyFocus(index.toString());
94
- }, 300);
95
- updateCache();
96
94
  },
97
- [updateCache, onlyFocus, vScroll]
95
+ [messages, trackFocusMessage, vScroll]
98
96
  );
99
97
  const checkPendingOnScroll = import_react.default.useCallback(() => {
100
- if (pendingOnScroll.current === true) {
101
- updateCache();
102
- onScrollStop();
103
- }
104
- }, [updateCache, onScrollStop, pendingOnScroll]);
98
+ if (pendingOnScroll.current === true) onScrollStop();
99
+ }, [onScrollStop, pendingOnScroll]);
105
100
  return import_react.default.useMemo(
106
101
  () => ({
102
+ vScroll,
107
103
  onScrollStop,
108
- updateCache,
109
104
  getMessagesInView,
110
105
  onlyFocus,
111
106
  scrollAndFocus,
112
107
  checkPendingOnScroll
113
108
  }),
114
- [onScrollStop, updateCache, getMessagesInView, onlyFocus, scrollAndFocus, checkPendingOnScroll]
109
+ [vScroll, onScrollStop, getMessagesInView, onlyFocus, scrollAndFocus, checkPendingOnScroll]
115
110
  );
116
111
  };
117
112
  //# sourceMappingURL=useFocusAndScrollHelpers.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/parts/chat-container-messages-list/useFocusAndScrollHelpers.ts", "../../../../../../../scripts/build/transpile/react-shim.js"],
4
- "sourcesContent": ["/* eslint-disable complexity */\n/* eslint-disable jsx-a11y/no-noninteractive-tabindex */\n/* eslint-disable max-lines */\nimport React from 'react';\nimport type { useVirtual } from 'react-virtual';\nimport { debounce, throttle } from 'lodash-es';\nimport type { DSChatT } from '../../react-desc-prop-types.js';\n\ntype UseFocusAndScrollHelpersArgs = {\n propsWithDefault: DSChatT.MessagesListProps;\n useVirtualHelpers: ReturnType<typeof useVirtual>;\n pendingOnScroll: React.MutableRefObject<boolean>;\n virtualListRef: React.MutableRefObject<HTMLDivElement | undefined>;\n};\n\nexport const useFocusAndScrollHelpers = ({\n propsWithDefault: { onScrollEnds },\n useVirtualHelpers: { scrollToIndex: vScroll },\n pendingOnScroll,\n virtualListRef,\n}: UseFocusAndScrollHelpersArgs) => {\n // Pure DOM read \u2014 no side effects beyond pendingOnScroll flag and getBoundingClientRect queries.\n const computeVisibleItems = React.useCallback((): string[] => {\n if (document.visibilityState === 'hidden') {\n pendingOnScroll.current = true;\n return [];\n }\n if (!virtualListRef.current) return [];\n pendingOnScroll.current = false;\n const { bottom, top } = virtualListRef.current.getBoundingClientRect();\n const list = virtualListRef.current.querySelectorAll<HTMLElement>('[data-element=\"chat-element-list-item\"]');\n const indexList: string[] = [];\n list.forEach((item: HTMLElement) => {\n const { top: itemTop } = item.getBoundingClientRect();\n const itemPadding = 10;\n if (itemTop + itemPadding < bottom && itemTop + itemPadding > top) indexList.push(item.dataset.index || '');\n });\n return indexList;\n }, [pendingOnScroll, virtualListRef]);\n\n // Throttled with leading: populates the cache immediately on first call and keeps it\n // fresh. No side effects beyond DOM reads \u2014 onScrollEnds is not involved.\n const visibleItemsCache = React.useRef<string[]>([]);\n const updateCache = React.useMemo(\n () =>\n throttle(\n () => {\n visibleItemsCache.current = computeVisibleItems();\n },\n 500,\n { leading: true },\n ),\n [computeVisibleItems],\n );\n\n // Synchronous getter \u2014 returns a copy so callers using .pop()/.shift() don't mutate the cache.\n const getMessagesInView = React.useCallback((): string[] => [...visibleItemsCache.current], []);\n\n // Debounced trailing: notifies onScrollEnds consumers after scrolling settles.\n // Semantics and timing unchanged \u2014 this path is isolated from the cache.\n const onScrollStop = React.useMemo(\n () =>\n debounce((): string[] => {\n const indexList = computeVisibleItems();\n if (onScrollEnds) onScrollEnds(indexList);\n return indexList;\n }, 500),\n [computeVisibleItems, onScrollEnds],\n ) as () => string[];\n\n const onlyFocus = React.useCallback(\n (index: string) => {\n (virtualListRef.current?.querySelector(`[data-index=\"${index}\"]`) as HTMLDivElement)?.focus();\n },\n [virtualListRef],\n );\n\n const scrollAndFocus = React.useCallback(\n (index: number) => {\n vScroll(index, { align: 'center' });\n /* [PUI-15629](https://jira.elliemae.io/browse/PUI-15629)\n * requestAnimationFrame is needed because the first scroll might not calculate positions\n * correctly if the DOM hasn't been fully updated yet. By waiting for the next animation\n * frame, we ensure that:\n * 1. The DOM has been updated with new positions after the first scroll\n * 2. Element height and position measurements are accurate\n * 3. The second scroll can center the element with correct measurements\n *\n * TODO: It might be worth consider upgrading to react-virtual v3(@tanstack/react-virtual), which handles DOM measurements and scroll\n * synchronization better. This RAF workaround might not be necessary with v3.\n */\n requestAnimationFrame(() => {\n vScroll(index, { align: 'center' });\n });\n\n setTimeout(() => {\n onlyFocus(index.toString());\n }, 300);\n\n updateCache();\n },\n [updateCache, onlyFocus, vScroll],\n );\n\n const checkPendingOnScroll = React.useCallback(() => {\n if (pendingOnScroll.current === true) {\n updateCache();\n onScrollStop();\n }\n }, [updateCache, onScrollStop, pendingOnScroll]);\n\n return React.useMemo(\n () => ({\n onScrollStop,\n updateCache,\n getMessagesInView,\n onlyFocus,\n scrollAndFocus,\n checkPendingOnScroll,\n }),\n [onScrollStop, updateCache, getMessagesInView, onlyFocus, scrollAndFocus, checkPendingOnScroll],\n );\n};\n", "import * as React from 'react';\nexport { React };\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;ADGvB,mBAAkB;AAElB,uBAAmC;AAU5B,MAAM,2BAA2B,CAAC;AAAA,EACvC,kBAAkB,EAAE,aAAa;AAAA,EACjC,mBAAmB,EAAE,eAAe,QAAQ;AAAA,EAC5C;AAAA,EACA;AACF,MAAoC;AAElC,QAAM,sBAAsB,aAAAA,QAAM,YAAY,MAAgB;AAC5D,QAAI,SAAS,oBAAoB,UAAU;AACzC,sBAAgB,UAAU;AAC1B,aAAO,CAAC;AAAA,IACV;AACA,QAAI,CAAC,eAAe,QAAS,QAAO,CAAC;AACrC,oBAAgB,UAAU;AAC1B,UAAM,EAAE,QAAQ,IAAI,IAAI,eAAe,QAAQ,sBAAsB;AACrE,UAAM,OAAO,eAAe,QAAQ,iBAA8B,yCAAyC;AAC3G,UAAM,YAAsB,CAAC;AAC7B,SAAK,QAAQ,CAAC,SAAsB;AAClC,YAAM,EAAE,KAAK,QAAQ,IAAI,KAAK,sBAAsB;AACpD,YAAM,cAAc;AACpB,UAAI,UAAU,cAAc,UAAU,UAAU,cAAc,IAAK,WAAU,KAAK,KAAK,QAAQ,SAAS,EAAE;AAAA,IAC5G,CAAC;AACD,WAAO;AAAA,EACT,GAAG,CAAC,iBAAiB,cAAc,CAAC;AAIpC,QAAM,oBAAoB,aAAAA,QAAM,OAAiB,CAAC,CAAC;AACnD,QAAM,cAAc,aAAAA,QAAM;AAAA,IACxB,UACE;AAAA,MACE,MAAM;AACJ,0BAAkB,UAAU,oBAAoB;AAAA,MAClD;AAAA,MACA;AAAA,MACA,EAAE,SAAS,KAAK;AAAA,IAClB;AAAA,IACF,CAAC,mBAAmB;AAAA,EACtB;AAGA,QAAM,oBAAoB,aAAAA,QAAM,YAAY,MAAgB,CAAC,GAAG,kBAAkB,OAAO,GAAG,CAAC,CAAC;AAI9F,QAAM,eAAe,aAAAA,QAAM;AAAA,IACzB,UACE,2BAAS,MAAgB;AACvB,YAAM,YAAY,oBAAoB;AACtC,UAAI,aAAc,cAAa,SAAS;AACxC,aAAO;AAAA,IACT,GAAG,GAAG;AAAA,IACR,CAAC,qBAAqB,YAAY;AAAA,EACpC;AAEA,QAAM,YAAY,aAAAA,QAAM;AAAA,IACtB,CAAC,UAAkB;AACjB,MAAC,eAAe,SAAS,cAAc,gBAAgB,KAAK,IAAI,GAAsB,MAAM;AAAA,IAC9F;AAAA,IACA,CAAC,cAAc;AAAA,EACjB;AAEA,QAAM,iBAAiB,aAAAA,QAAM;AAAA,IAC3B,CAAC,UAAkB;AACjB,cAAQ,OAAO,EAAE,OAAO,SAAS,CAAC;AAYlC,4BAAsB,MAAM;AAC1B,gBAAQ,OAAO,EAAE,OAAO,SAAS,CAAC;AAAA,MACpC,CAAC;AAED,iBAAW,MAAM;AACf,kBAAU,MAAM,SAAS,CAAC;AAAA,MAC5B,GAAG,GAAG;AAEN,kBAAY;AAAA,IACd;AAAA,IACA,CAAC,aAAa,WAAW,OAAO;AAAA,EAClC;AAEA,QAAM,uBAAuB,aAAAA,QAAM,YAAY,MAAM;AACnD,QAAI,gBAAgB,YAAY,MAAM;AACpC,kBAAY;AACZ,mBAAa;AAAA,IACf;AAAA,EACF,GAAG,CAAC,aAAa,cAAc,eAAe,CAAC;AAE/C,SAAO,aAAAA,QAAM;AAAA,IACX,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,CAAC,cAAc,aAAa,mBAAmB,WAAW,gBAAgB,oBAAoB;AAAA,EAChG;AACF;",
4
+ "sourcesContent": ["/* eslint-disable complexity */\n/* eslint-disable jsx-a11y/no-noninteractive-tabindex */\n/* eslint-disable max-lines */\nimport React from 'react';\nimport { debounce } from 'lodash-es';\nimport type { DSFastListT } from '@elliemae/ds-fast-list';\nimport type { DSChatT } from '../../react-desc-prop-types.js';\nimport { getMessageFocusId, getMessagePositionId } from './useMessageFocusTracker.js';\nimport type { MessageId } from './useMessageFocusTracker.js';\n\ntype UseFocusAndScrollHelpersArgs = {\n propsWithDefault: DSChatT.MessagesListProps;\n fastListActionRef: React.MutableRefObject<DSFastListT.ActionRef>;\n pendingOnScroll: React.MutableRefObject<boolean>;\n trackFocusMessage: (messageId: MessageId, options?: { preventScroll?: boolean }) => void;\n};\n\nexport const useFocusAndScrollHelpers = ({\n propsWithDefault: { onScrollEnds, messages },\n fastListActionRef,\n pendingOnScroll,\n trackFocusMessage,\n}: UseFocusAndScrollHelpersArgs) => {\n /**\n * Does not use `scrollTo` (the virtualizer's `scrollToIndex`): since @tanstack/react-virtual v3 every call\n * starts a loop that checks the position on the following frames and scrolls again, up to 10 times, and it\n * cannot tell the user's scrolling from its own, so a user who scrolls while it runs is pulled back to the\n * target over and over (PUI-19237). This scrolls the way react-virtual v2's `scrollToIndex` did instead:\n * once now and once on the next frame, when the items just rendered have been measured, and never again.\n */\n const vScroll = React.useCallback(\n (index: number, opts?: DSChatT.ScrollActionOptions) => {\n const scrollOnce = () => {\n const fastList = fastListActionRef.current;\n /** The chat's own default alignment also applies when only `behavior` is provided. */\n const offset = fastList.getOffsetForIndex?.(index, opts?.align ?? 'center');\n if (offset !== undefined) fastList.scrollToOffset?.(offset, { behavior: opts?.behavior });\n };\n scrollOnce();\n requestAnimationFrame(scrollOnce);\n },\n [fastListActionRef],\n );\n\n /**\n * Evaluated at call time from `ds-fast-list`'s `getVisibleItems()` (the virtualizer's own pre-overscan\n * viewport range), so it always reflects the current viewport, including after the user scrolls by hand.\n * It is empty before the container's first real measurement.\n */\n const computeVisibleItems = React.useCallback((): string[] => {\n if (document.visibilityState === 'hidden') {\n pendingOnScroll.current = true;\n return [];\n }\n const visibleItems = fastListActionRef.current.getVisibleItems?.() ?? [];\n if (visibleItems.length === 0) return [];\n pendingOnScroll.current = false;\n return visibleItems.map(String);\n }, [fastListActionRef, pendingOnScroll]);\n\n // Each call returns a fresh array, so callers using .pop()/.shift() can't affect later calls.\n const getMessagesInView = computeVisibleItems;\n\n // Debounced trailing: notifies onScrollEnds consumers after scrolling settles.\n const onScrollStop = React.useMemo(\n () =>\n debounce((): string[] => {\n const indexList = computeVisibleItems();\n if (onScrollEnds) onScrollEnds(indexList);\n return indexList;\n }, 500),\n [computeVisibleItems, onScrollEnds],\n ) as () => string[];\n\n /**\n * Focuses a message only if it is mounted now: it never focuses one later, when it scrolls into view. It never\n * scrolls either, as its name says, even when the message is only partly visible.\n */\n const onlyFocus = React.useCallback(\n (index: string) => {\n const position = Number(index);\n const row = messages?.[position];\n const mounted = fastListActionRef.current.getMountedItems?.() ?? [];\n if (row && mounted.includes(position))\n trackFocusMessage(getMessageFocusId(row, position), { preventScroll: true });\n },\n [messages, fastListActionRef, trackFocusMessage],\n );\n\n const scrollAndFocus = React.useCallback(\n (index: number, opts: DSChatT.ScrollActionOptions = { align: 'center' }) => {\n const row = messages?.[index];\n /** A message not rendered yet (added in the same handler) is targeted by its position; `vScroll`'s second write runs after the commit. */\n const messageId = row ? getMessageFocusId(row, index) : getMessagePositionId(index);\n trackFocusMessage(messageId, { preventScroll: true });\n vScroll(index, opts);\n /* [PUI-15629](https://jira.elliemae.io/browse/PUI-15629)\n * requestAnimationFrame is needed because the first scroll might not calculate positions\n * correctly if the DOM hasn't been fully updated yet. By waiting for the next animation\n * frame, we ensure that:\n * 1. The DOM has been updated with new positions after the first scroll\n * 2. Element height and position measurements are accurate\n * 3. The second scroll can align the element with correct measurements\n */\n requestAnimationFrame(() => {\n vScroll(index, opts);\n });\n },\n [messages, trackFocusMessage, vScroll],\n );\n\n const checkPendingOnScroll = React.useCallback(() => {\n if (pendingOnScroll.current === true) onScrollStop();\n }, [onScrollStop, pendingOnScroll]);\n\n return React.useMemo(\n () => ({\n vScroll,\n onScrollStop,\n getMessagesInView,\n onlyFocus,\n scrollAndFocus,\n checkPendingOnScroll,\n }),\n [vScroll, onScrollStop, getMessagesInView, onlyFocus, scrollAndFocus, checkPendingOnScroll],\n );\n};\n", "import * as React from 'react';\nexport { React };\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;ADGvB,mBAAkB;AAClB,uBAAyB;AAGzB,oCAAwD;AAUjD,MAAM,2BAA2B,CAAC;AAAA,EACvC,kBAAkB,EAAE,cAAc,SAAS;AAAA,EAC3C;AAAA,EACA;AAAA,EACA;AACF,MAAoC;AAQlC,QAAM,UAAU,aAAAA,QAAM;AAAA,IACpB,CAAC,OAAe,SAAuC;AACrD,YAAM,aAAa,MAAM;AACvB,cAAM,WAAW,kBAAkB;AAEnC,cAAM,SAAS,SAAS,oBAAoB,OAAO,MAAM,SAAS,QAAQ;AAC1E,YAAI,WAAW,OAAW,UAAS,iBAAiB,QAAQ,EAAE,UAAU,MAAM,SAAS,CAAC;AAAA,MAC1F;AACA,iBAAW;AACX,4BAAsB,UAAU;AAAA,IAClC;AAAA,IACA,CAAC,iBAAiB;AAAA,EACpB;AAOA,QAAM,sBAAsB,aAAAA,QAAM,YAAY,MAAgB;AAC5D,QAAI,SAAS,oBAAoB,UAAU;AACzC,sBAAgB,UAAU;AAC1B,aAAO,CAAC;AAAA,IACV;AACA,UAAM,eAAe,kBAAkB,QAAQ,kBAAkB,KAAK,CAAC;AACvE,QAAI,aAAa,WAAW,EAAG,QAAO,CAAC;AACvC,oBAAgB,UAAU;AAC1B,WAAO,aAAa,IAAI,MAAM;AAAA,EAChC,GAAG,CAAC,mBAAmB,eAAe,CAAC;AAGvC,QAAM,oBAAoB;AAG1B,QAAM,eAAe,aAAAA,QAAM;AAAA,IACzB,UACE,2BAAS,MAAgB;AACvB,YAAM,YAAY,oBAAoB;AACtC,UAAI,aAAc,cAAa,SAAS;AACxC,aAAO;AAAA,IACT,GAAG,GAAG;AAAA,IACR,CAAC,qBAAqB,YAAY;AAAA,EACpC;AAMA,QAAM,YAAY,aAAAA,QAAM;AAAA,IACtB,CAAC,UAAkB;AACjB,YAAM,WAAW,OAAO,KAAK;AAC7B,YAAM,MAAM,WAAW,QAAQ;AAC/B,YAAM,UAAU,kBAAkB,QAAQ,kBAAkB,KAAK,CAAC;AAClE,UAAI,OAAO,QAAQ,SAAS,QAAQ;AAClC,8BAAkB,iDAAkB,KAAK,QAAQ,GAAG,EAAE,eAAe,KAAK,CAAC;AAAA,IAC/E;AAAA,IACA,CAAC,UAAU,mBAAmB,iBAAiB;AAAA,EACjD;AAEA,QAAM,iBAAiB,aAAAA,QAAM;AAAA,IAC3B,CAAC,OAAe,OAAoC,EAAE,OAAO,SAAS,MAAM;AAC1E,YAAM,MAAM,WAAW,KAAK;AAE5B,YAAM,YAAY,UAAM,iDAAkB,KAAK,KAAK,QAAI,oDAAqB,KAAK;AAClF,wBAAkB,WAAW,EAAE,eAAe,KAAK,CAAC;AACpD,cAAQ,OAAO,IAAI;AASnB,4BAAsB,MAAM;AAC1B,gBAAQ,OAAO,IAAI;AAAA,MACrB,CAAC;AAAA,IACH;AAAA,IACA,CAAC,UAAU,mBAAmB,OAAO;AAAA,EACvC;AAEA,QAAM,uBAAuB,aAAAA,QAAM,YAAY,MAAM;AACnD,QAAI,gBAAgB,YAAY,KAAM,cAAa;AAAA,EACrD,GAAG,CAAC,cAAc,eAAe,CAAC;AAElC,SAAO,aAAAA,QAAM;AAAA,IACX,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,CAAC,SAAS,cAAc,mBAAmB,WAAW,gBAAgB,oBAAoB;AAAA,EAC5F;AACF;",
6
6
  "names": ["React"]
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/parts/chat-container-messages-list/useGlobalEventListener.tsx", "../../../../../../../scripts/build/transpile/react-shim.js"],
4
- "sourcesContent": ["import React from 'react';\nimport { type TypescriptHelpersT } from '@elliemae/ds-typescript-helpers';\nimport { type DSChatT } from '../../react-desc-prop-types.js';\n\ntype UseGlobalEventListenerArgs = {\n propsWithDefault: DSChatT.MessagesListProps;\n virtualListRef: React.MutableRefObject<HTMLDivElement | undefined>;\n onScrollStop: () => string[];\n checkPendingOnScroll: TypescriptHelpersT.GenericFunc;\n};\n\nexport const useGlobalEventListener = ({\n propsWithDefault: { onScrollEnds },\n virtualListRef,\n onScrollStop,\n checkPendingOnScroll,\n}: UseGlobalEventListenerArgs) => {\n React.useEffect(() => {\n let handlerCallback: ReturnType<typeof setTimeout>;\n const localVirtualRef = virtualListRef.current;\n const onScroll = () => {\n if (handlerCallback) clearTimeout(handlerCallback);\n handlerCallback = setTimeout(onScrollStop, 300);\n };\n if (onScrollEnds) {\n virtualListRef.current?.addEventListener('scroll', onScroll);\n }\n document.addEventListener('visibilitychange', checkPendingOnScroll);\n return () => {\n if (handlerCallback) clearTimeout(handlerCallback);\n if (onScrollEnds && localVirtualRef) localVirtualRef.removeEventListener('scroll', onScroll);\n document.removeEventListener('visibilitychange', checkPendingOnScroll);\n };\n }, [onScrollStop, onScrollEnds, checkPendingOnScroll, virtualListRef]);\n};\n", "import * as React from 'react';\nexport { React };\n"],
4
+ "sourcesContent": ["import React from 'react';\nimport { type TypescriptHelpersT } from '@elliemae/ds-typescript-helpers';\nimport { type DSChatT } from '../../react-desc-prop-types.js';\n\ntype UseGlobalEventListenerArgs = {\n propsWithDefault: DSChatT.MessagesListProps;\n virtualListRef: React.MutableRefObject<HTMLDivElement | null>;\n onScrollStop: () => string[];\n checkPendingOnScroll: TypescriptHelpersT.GenericFunc;\n};\n\nexport const useGlobalEventListener = ({\n propsWithDefault: { onScrollEnds },\n virtualListRef,\n onScrollStop,\n checkPendingOnScroll,\n}: UseGlobalEventListenerArgs) => {\n React.useEffect(() => {\n let handlerCallback: ReturnType<typeof setTimeout>;\n const localVirtualRef = virtualListRef.current;\n const onScroll = () => {\n if (handlerCallback) clearTimeout(handlerCallback);\n handlerCallback = setTimeout(onScrollStop, 300);\n };\n if (onScrollEnds) {\n virtualListRef.current?.addEventListener('scroll', onScroll);\n }\n document.addEventListener('visibilitychange', checkPendingOnScroll);\n return () => {\n if (handlerCallback) clearTimeout(handlerCallback);\n if (onScrollEnds && localVirtualRef) localVirtualRef.removeEventListener('scroll', onScroll);\n document.removeEventListener('visibilitychange', checkPendingOnScroll);\n };\n }, [onScrollStop, onScrollEnds, checkPendingOnScroll, virtualListRef]);\n};\n", "import * as React from 'react';\nexport { React };\n"],
5
5
  "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;ADAvB,mBAAkB;AAWX,MAAM,yBAAyB,CAAC;AAAA,EACrC,kBAAkB,EAAE,aAAa;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AACF,MAAkC;AAChC,eAAAA,QAAM,UAAU,MAAM;AACpB,QAAI;AACJ,UAAM,kBAAkB,eAAe;AACvC,UAAM,WAAW,MAAM;AACrB,UAAI,gBAAiB,cAAa,eAAe;AACjD,wBAAkB,WAAW,cAAc,GAAG;AAAA,IAChD;AACA,QAAI,cAAc;AAChB,qBAAe,SAAS,iBAAiB,UAAU,QAAQ;AAAA,IAC7D;AACA,aAAS,iBAAiB,oBAAoB,oBAAoB;AAClE,WAAO,MAAM;AACX,UAAI,gBAAiB,cAAa,eAAe;AACjD,UAAI,gBAAgB,gBAAiB,iBAAgB,oBAAoB,UAAU,QAAQ;AAC3F,eAAS,oBAAoB,oBAAoB,oBAAoB;AAAA,IACvE;AAAA,EACF,GAAG,CAAC,cAAc,cAAc,sBAAsB,cAAc,CAAC;AACvE;",
6
6
  "names": ["React"]
7
7
  }
@@ -33,6 +33,7 @@ __export(useKeyboard_exports, {
33
33
  module.exports = __toCommonJS(useKeyboard_exports);
34
34
  var React = __toESM(require("react"));
35
35
  var import_react = __toESM(require("react"));
36
+ var import_useMessageFocusTracker = require("./useMessageFocusTracker.js");
36
37
  const getIsNumber = (val) => Number.isInteger(val);
37
38
  const useKeyboard = ({
38
39
  propsWithDefault: { hasMoreItems, messages, bannerPosition, bannerRef },
@@ -41,7 +42,8 @@ const useKeyboard = ({
41
42
  loadMoreRef,
42
43
  scrollAndFocus,
43
44
  onlyFocus,
44
- getMessagesInView
45
+ getMessagesInView,
46
+ trackFocusMessage
45
47
  }) => {
46
48
  const handleKey = import_react.default.useCallback(
47
49
  (e) => {
@@ -53,7 +55,7 @@ const useKeyboard = ({
53
55
  case "ArrowUp":
54
56
  e.preventDefault();
55
57
  if (index && index > 0) {
56
- (virtualListRef.current?.querySelector(`[data-index="${index - 1}"]`)).focus();
58
+ trackFocusMessage((0, import_useMessageFocusTracker.getMessageFocusId)(messages[index - 1], index - 1));
57
59
  } else if (hasMoreItems === "top") {
58
60
  loadMoreRef.current?.focus();
59
61
  }
@@ -61,7 +63,7 @@ const useKeyboard = ({
61
63
  case "ArrowDown":
62
64
  e.preventDefault();
63
65
  if (messages?.length && isIndexNumber && index < messages.length - 1) {
64
- (virtualListRef.current?.querySelector(`[data-index="${index + 1}"]`)).focus();
66
+ trackFocusMessage((0, import_useMessageFocusTracker.getMessageFocusId)(messages[index + 1], index + 1));
65
67
  }
66
68
  break;
67
69
  case "Home":
@@ -93,7 +95,7 @@ const useKeyboard = ({
93
95
  break;
94
96
  }
95
97
  },
96
- [hasMoreItems, messages.length, scrollAndFocus, virtualListRef, loadMoreRef, bannerPosition, bannerRef]
98
+ [hasMoreItems, messages, scrollAndFocus, virtualListRef, loadMoreRef, bannerPosition, bannerRef, trackFocusMessage]
97
99
  );
98
100
  const handleListKey = import_react.default.useCallback(
99
101
  (e) => {
@@ -109,7 +111,7 @@ const useKeyboard = ({
109
111
  case "ArrowDown":
110
112
  e.preventDefault();
111
113
  if (document.activeElement === loadMoreRef.current) {
112
- virtualListRef.current?.querySelector(`[data-index="0"]`)?.focus();
114
+ onlyFocus("0");
113
115
  } else {
114
116
  const id = getMessagesInView()?.pop();
115
117
  if (id) onlyFocus(id);
@@ -144,16 +146,7 @@ const useKeyboard = ({
144
146
  break;
145
147
  }
146
148
  },
147
- [
148
- actionRef,
149
- hasMoreItems,
150
- loadMoreRef,
151
- messages.length,
152
- getMessagesInView,
153
- onlyFocus,
154
- scrollAndFocus,
155
- virtualListRef
156
- ]
149
+ [actionRef, hasMoreItems, loadMoreRef, messages, getMessagesInView, onlyFocus, scrollAndFocus, virtualListRef]
157
150
  );
158
151
  return import_react.default.useMemo(() => ({ handleKey, handleListKey }), [handleKey, handleListKey]);
159
152
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/parts/chat-container-messages-list/useKeyboard.ts", "../../../../../../../scripts/build/transpile/react-shim.js"],
4
- "sourcesContent": ["/* eslint-disable complexity */\n/* eslint-disable jsx-a11y/no-noninteractive-tabindex */\n/* eslint-disable max-lines */\nimport React from 'react';\nimport type { DSChatT } from '../../react-desc-prop-types.js';\n\ntype UseKeyboardArgs = {\n propsWithDefault: DSChatT.MessagesListProps;\n actionRef: DSChatT.ActionRef;\n virtualListRef: React.MutableRefObject<HTMLDivElement | undefined>;\n loadMoreRef: React.MutableRefObject<HTMLButtonElement | undefined>;\n scrollAndFocus: (index: number, opts?: DSChatT.ScrollToIndexOptions) => void;\n onlyFocus: (index: string) => void;\n getMessagesInView: () => string[];\n};\n\nconst getIsNumber = (val: unknown): val is number => Number.isInteger(val);\n\nexport const useKeyboard = ({\n propsWithDefault: { hasMoreItems, messages, bannerPosition, bannerRef },\n actionRef,\n virtualListRef,\n loadMoreRef,\n scrollAndFocus,\n onlyFocus,\n getMessagesInView,\n}: UseKeyboardArgs) => {\n const handleKey = React.useCallback(\n (e: React.KeyboardEvent<HTMLElement>) => {\n const { key, currentTarget } = e;\n const index = currentTarget.dataset?.index ? Number(currentTarget.dataset.index) : undefined;\n const isIndexNumber = getIsNumber(index);\n e.stopPropagation();\n switch (key) {\n case 'ArrowUp':\n e.preventDefault();\n if (index && index > 0) {\n (virtualListRef.current?.querySelector(`[data-index=\"${index - 1}\"]`) as HTMLDivElement).focus();\n } else if (hasMoreItems === 'top') {\n loadMoreRef.current?.focus();\n }\n break;\n case 'ArrowDown':\n e.preventDefault();\n if (messages?.length && isIndexNumber && index < messages.length - 1) {\n (virtualListRef.current?.querySelector(`[data-index=\"${index + 1}\"]`) as HTMLDivElement).focus();\n }\n break;\n case 'Home':\n e.preventDefault();\n scrollAndFocus(0);\n break;\n case 'End':\n e.preventDefault();\n if (messages?.length) scrollAndFocus(messages.length - 1);\n break;\n case 'Tab':\n if (hasMoreItems === 'bottom' && !e.shiftKey) {\n e.preventDefault();\n loadMoreRef.current?.focus();\n } else if (hasMoreItems === 'top' && e.shiftKey) {\n e.preventDefault();\n loadMoreRef.current?.focus();\n } else if (!e.shiftKey) {\n if (bannerPosition) {\n e.preventDefault();\n bannerRef?.current?.focusOnWrapper();\n }\n } else {\n e.preventDefault();\n virtualListRef.current?.focus();\n }\n break;\n default:\n break;\n }\n },\n [hasMoreItems, messages.length, scrollAndFocus, virtualListRef, loadMoreRef, bannerPosition, bannerRef],\n );\n const handleListKey = React.useCallback(\n (e: React.KeyboardEvent) => {\n const { key } = e;\n switch (key) {\n case 'ArrowUp':\n e.preventDefault();\n if (document.activeElement !== loadMoreRef.current) {\n const id = getMessagesInView()?.shift();\n if (id) onlyFocus(id);\n }\n break;\n case 'ArrowDown':\n e.preventDefault();\n if (document.activeElement === loadMoreRef.current) {\n (virtualListRef.current?.querySelector(`[data-index=\"0\"]`) as HTMLDivElement)?.focus();\n } else {\n const id = getMessagesInView()?.pop();\n if (id) onlyFocus(id);\n }\n break;\n case 'Home':\n e.preventDefault();\n scrollAndFocus(0);\n break;\n case 'End':\n e.preventDefault();\n if (messages?.length) scrollAndFocus(messages.length - 1);\n break;\n case 'Tab':\n if (document.activeElement !== loadMoreRef.current) {\n if (hasMoreItems === 'bottom' && !e.shiftKey) {\n e.preventDefault();\n loadMoreRef.current?.focus();\n } else if (!e.shiftKey) {\n e.preventDefault();\n actionRef.current?.focusToComposer?.();\n }\n } else if (!e.shiftKey) {\n e.preventDefault();\n actionRef.current?.focusToComposer?.();\n } else {\n e.preventDefault();\n virtualListRef.current?.focus();\n }\n break;\n default:\n break;\n }\n },\n [\n actionRef,\n hasMoreItems,\n loadMoreRef,\n messages.length,\n getMessagesInView,\n onlyFocus,\n scrollAndFocus,\n virtualListRef,\n ],\n );\n\n return React.useMemo(() => ({ handleKey, handleListKey }), [handleKey, handleListKey]);\n};\n", "import * as React from 'react';\nexport { React };\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/* eslint-disable complexity */\n/* eslint-disable jsx-a11y/no-noninteractive-tabindex */\n/* eslint-disable max-lines */\nimport React from 'react';\nimport type { DSChatT } from '../../react-desc-prop-types.js';\nimport { getMessageFocusId } from './useMessageFocusTracker.js';\nimport type { MessageId } from './useMessageFocusTracker.js';\n\ntype UseKeyboardArgs = {\n propsWithDefault: DSChatT.MessagesListProps;\n actionRef: DSChatT.ActionRef;\n virtualListRef: React.MutableRefObject<HTMLDivElement | null>;\n loadMoreRef: React.MutableRefObject<HTMLButtonElement | undefined>;\n scrollAndFocus: (index: number, opts?: DSChatT.ScrollActionOptions) => void;\n onlyFocus: (index: string) => void;\n getMessagesInView: () => string[];\n trackFocusMessage: (messageId: MessageId, options?: { preventScroll?: boolean }) => void;\n};\n\nconst getIsNumber = (val: unknown): val is number => Number.isInteger(val);\n\nexport const useKeyboard = ({\n propsWithDefault: { hasMoreItems, messages, bannerPosition, bannerRef },\n actionRef,\n virtualListRef,\n loadMoreRef,\n scrollAndFocus,\n onlyFocus,\n getMessagesInView,\n trackFocusMessage,\n}: UseKeyboardArgs) => {\n const handleKey = React.useCallback(\n (e: React.KeyboardEvent<HTMLElement>) => {\n const { key, currentTarget } = e;\n const index = currentTarget.dataset?.index ? Number(currentTarget.dataset.index) : undefined;\n const isIndexNumber = getIsNumber(index);\n e.stopPropagation();\n switch (key) {\n case 'ArrowUp':\n e.preventDefault();\n if (index && index > 0) {\n trackFocusMessage(getMessageFocusId(messages[index - 1], index - 1));\n } else if (hasMoreItems === 'top') {\n loadMoreRef.current?.focus();\n }\n break;\n case 'ArrowDown':\n e.preventDefault();\n if (messages?.length && isIndexNumber && index < messages.length - 1) {\n trackFocusMessage(getMessageFocusId(messages[index + 1], index + 1));\n }\n break;\n case 'Home':\n e.preventDefault();\n scrollAndFocus(0);\n break;\n case 'End':\n e.preventDefault();\n if (messages?.length) scrollAndFocus(messages.length - 1);\n break;\n case 'Tab':\n if (hasMoreItems === 'bottom' && !e.shiftKey) {\n e.preventDefault();\n loadMoreRef.current?.focus();\n } else if (hasMoreItems === 'top' && e.shiftKey) {\n e.preventDefault();\n loadMoreRef.current?.focus();\n } else if (!e.shiftKey) {\n if (bannerPosition) {\n e.preventDefault();\n bannerRef?.current?.focusOnWrapper();\n }\n } else {\n e.preventDefault();\n virtualListRef.current?.focus();\n }\n break;\n default:\n break;\n }\n },\n [hasMoreItems, messages, scrollAndFocus, virtualListRef, loadMoreRef, bannerPosition, bannerRef, trackFocusMessage],\n );\n const handleListKey = React.useCallback(\n (e: React.KeyboardEvent) => {\n const { key } = e;\n switch (key) {\n case 'ArrowUp':\n e.preventDefault();\n if (document.activeElement !== loadMoreRef.current) {\n const id = getMessagesInView()?.shift();\n if (id) onlyFocus(id);\n }\n break;\n case 'ArrowDown':\n e.preventDefault();\n if (document.activeElement === loadMoreRef.current) {\n onlyFocus('0');\n } else {\n const id = getMessagesInView()?.pop();\n if (id) onlyFocus(id);\n }\n break;\n case 'Home':\n e.preventDefault();\n scrollAndFocus(0);\n break;\n case 'End':\n e.preventDefault();\n if (messages?.length) scrollAndFocus(messages.length - 1);\n break;\n case 'Tab':\n if (document.activeElement !== loadMoreRef.current) {\n if (hasMoreItems === 'bottom' && !e.shiftKey) {\n e.preventDefault();\n loadMoreRef.current?.focus();\n } else if (!e.shiftKey) {\n e.preventDefault();\n actionRef.current?.focusToComposer?.();\n }\n } else if (!e.shiftKey) {\n e.preventDefault();\n actionRef.current?.focusToComposer?.();\n } else {\n e.preventDefault();\n virtualListRef.current?.focus();\n }\n break;\n default:\n break;\n }\n },\n [actionRef, hasMoreItems, loadMoreRef, messages, getMessagesInView, onlyFocus, scrollAndFocus, virtualListRef],\n );\n\n return React.useMemo(() => ({ handleKey, handleListKey }), [handleKey, handleListKey]);\n};\n", "import * as React from 'react';\nexport { React };\n"],
5
+ "mappings": "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",
6
6
  "names": ["React"]
7
7
  }
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var useMessageFocusTracker_exports = {};
30
+ __export(useMessageFocusTracker_exports, {
31
+ getMessageFocusId: () => getMessageFocusId,
32
+ getMessagePositionId: () => getMessagePositionId,
33
+ useMessageFocusTracker: () => useMessageFocusTracker
34
+ });
35
+ module.exports = __toCommonJS(useMessageFocusTracker_exports);
36
+ var React = __toESM(require("react"));
37
+ var import_react = __toESM(require("react"));
38
+ const getMessageFocusId = (message, index) => message.dsId === void 0 || message.dsId === null ? `index:${index}` : `id:${typeof message.dsId}:${message.dsId}`;
39
+ const getMessagePositionId = (index) => `position:${index}`;
40
+ const useMessageFocusTracker = (virtualListRef) => {
41
+ const [focusRequest, setFocusRequest] = import_react.default.useState(null);
42
+ const lastRequestId = import_react.default.useRef(0);
43
+ const fulfilledRequestId = import_react.default.useRef(0);
44
+ const requestOrigin = import_react.default.useRef(null);
45
+ const trackFocusMessage = import_react.default.useCallback(
46
+ (messageId, { preventScroll = false } = {}) => {
47
+ lastRequestId.current += 1;
48
+ requestOrigin.current = document.activeElement;
49
+ setFocusRequest({ messageId, requestId: lastRequestId.current, preventScroll });
50
+ },
51
+ []
52
+ );
53
+ const trackFocusResetter = import_react.default.useCallback(() => {
54
+ fulfilledRequestId.current = lastRequestId.current;
55
+ setFocusRequest(null);
56
+ }, []);
57
+ const trackFocusedMessage = import_react.default.useCallback((messageId) => {
58
+ fulfilledRequestId.current = lastRequestId.current;
59
+ setFocusRequest(
60
+ (previous) => previous?.messageId === messageId ? previous : { messageId, requestId: lastRequestId.current, preventScroll: false }
61
+ );
62
+ }, []);
63
+ const mayTakeFocus = import_react.default.useCallback(
64
+ (requestId, target) => {
65
+ const active = document.activeElement;
66
+ const isPending = requestId > fulfilledRequestId.current;
67
+ if (!active || active === document.body) return isPending ? "request" : "restore";
68
+ if (isPending && active === requestOrigin.current) return "request";
69
+ if (!target?.contains(active)) trackFocusResetter();
70
+ return null;
71
+ },
72
+ [trackFocusResetter]
73
+ );
74
+ const markFulfilled = import_react.default.useCallback((requestId) => {
75
+ fulfilledRequestId.current = Math.max(fulfilledRequestId.current, requestId);
76
+ }, []);
77
+ const listenersCleanup = import_react.default.useRef(null);
78
+ const listRootRef = import_react.default.useCallback(
79
+ (node) => {
80
+ listenersCleanup.current?.();
81
+ listenersCleanup.current = null;
82
+ if (!node) return;
83
+ const onDocumentFocusIn = (e) => {
84
+ const target = e.composedPath()[0] ?? e.target;
85
+ if (!virtualListRef.current?.contains(target)) trackFocusResetter();
86
+ };
87
+ const endPendingRequest = () => {
88
+ if (lastRequestId.current > fulfilledRequestId.current) trackFocusResetter();
89
+ };
90
+ const userScrollEvents = ["wheel", "touchstart", "pointerdown"];
91
+ document.addEventListener("focusin", onDocumentFocusIn);
92
+ userScrollEvents.forEach((type) => node.addEventListener(type, endPendingRequest, { passive: true }));
93
+ listenersCleanup.current = () => {
94
+ document.removeEventListener("focusin", onDocumentFocusIn);
95
+ userScrollEvents.forEach((type) => node.removeEventListener(type, endPendingRequest));
96
+ };
97
+ },
98
+ [virtualListRef, trackFocusResetter]
99
+ );
100
+ const handleFocusOut = import_react.default.useCallback(
101
+ (e) => {
102
+ const related = e.relatedTarget;
103
+ if (related && e.currentTarget.contains(related)) return;
104
+ if (!related && !document.hasFocus()) return;
105
+ trackFocusResetter();
106
+ },
107
+ [trackFocusResetter]
108
+ );
109
+ return import_react.default.useMemo(
110
+ () => ({
111
+ focusRequest,
112
+ trackFocusMessage,
113
+ trackFocusResetter,
114
+ trackFocusedMessage,
115
+ mayTakeFocus,
116
+ markFulfilled,
117
+ handleFocusOut,
118
+ listRootRef
119
+ }),
120
+ [
121
+ focusRequest,
122
+ trackFocusMessage,
123
+ trackFocusResetter,
124
+ trackFocusedMessage,
125
+ mayTakeFocus,
126
+ markFulfilled,
127
+ handleFocusOut,
128
+ listRootRef
129
+ ]
130
+ );
131
+ };
132
+ //# sourceMappingURL=useMessageFocusTracker.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/parts/chat-container-messages-list/useMessageFocusTracker.ts", "../../../../../../../scripts/build/transpile/react-shim.js"],
4
+ "sourcesContent": ["import React from 'react';\n\n/** Focus identity of a message, from `getMessageFocusId`. */\nexport type MessageId = string;\n\n/**\n * Identity the focus intent is kept by: the message's `dsId`, which survives index shifts. `dsId` is required by\n * the types but not at runtime, so a message without one falls back to its index (the focus then follows the\n * position, as it did before the focus tracker) instead of matching every other id-less message. The two forms\n * carry different prefixes so a `dsId` can never equal an index fallback, and the type of the `dsId` is kept so\n * `3` and `'3'` stay different messages, as with a direct comparison.\n */\nexport const getMessageFocusId = (message: { dsId?: string | number | null }, index: number): MessageId =>\n message.dsId === undefined || message.dsId === null ? `index:${index}` : `id:${typeof message.dsId}:${message.dsId}`;\n\n/**\n * Target by position, for a request made before the message exists in the rendered list (for example\n * `focusToIndex` called right after the consumer adds a message): the item at that index focuses itself when it\n * mounts, and its real focus then moves the intent to its identity.\n */\nexport const getMessagePositionId = (index: number): MessageId => `position:${index}`;\n\nexport type MessageFocusRequest = {\n /** Identity of the message to focus, from `getMessageFocusId`. */\n messageId: MessageId;\n /** Incremented by every explicit request, so asking again for the current target focuses it again. */\n requestId: number;\n /** `true` when the caller scrolls to the message itself, so focusing must not scroll as well. */\n preventScroll: boolean;\n};\n\ntype TrackFocusOptions = { preventScroll?: boolean };\n\n/**\n * Message focus through the focus-tracker pattern: handlers declare which message should have the focus, and\n * only that message's `ItemRenderer` focuses itself from its ref callback, once React has committed it. Nothing\n * here moves the focus directly.\n *\n * Removing a focused message never clears the intent: React does not dispatch `onBlur` for a focused node it\n * removes itself (measured for a prepend and for a message scrolled out of the virtual window). The intent is\n * cleared by real focus moves: leaving the list (`handleFocusOut`), a `focusin` on a non-message element of the\n * list, any `focusin` outside the list while an intent exists (also when the focus was on the page, where no\n * event crosses the list), and a remount that finds the focus elsewhere (`mayTakeFocus`).\n */\nexport const useMessageFocusTracker = (virtualListRef: React.MutableRefObject<HTMLDivElement | null>) => {\n const [focusRequest, setFocusRequest] = React.useState<MessageFocusRequest | null>(null);\n const lastRequestId = React.useRef(0);\n const fulfilledRequestId = React.useRef(0);\n /** The element that had the focus when the last explicit request was made. */\n const requestOrigin = React.useRef<Element | null>(null);\n\n const trackFocusMessage = React.useCallback(\n (messageId: MessageId, { preventScroll = false }: TrackFocusOptions = {}) => {\n lastRequestId.current += 1;\n requestOrigin.current = document.activeElement;\n setFocusRequest({ messageId, requestId: lastRequestId.current, preventScroll });\n },\n [],\n );\n\n const trackFocusResetter = React.useCallback(() => {\n fulfilledRequestId.current = lastRequestId.current;\n setFocusRequest(null);\n }, []);\n\n /**\n * Keeps the intent equal to the message that actually has the focus (the item itself or a descendant, such as a\n * link in its body), without re-focusing it. The real focus closes any pending request: this invariant keeps the\n * new intent passive, so the guard in `mayTakeFocus` neither takes the focus from the user nor drops the intent.\n */\n const trackFocusedMessage = React.useCallback((messageId: MessageId) => {\n fulfilledRequestId.current = lastRequestId.current;\n setFocusRequest((previous) =>\n previous?.messageId === messageId\n ? previous\n : { messageId, requestId: lastRequestId.current, preventScroll: false },\n );\n }, []);\n\n /**\n * Whether the target (the message's own item wrapper) may take the focus now, and how:\n * - `'request'`: an explicit request not yet fulfilled, from `body` or from the element focused when it was made;\n * it focuses with the request's own `preventScroll`.\n * - `'restore'`: a target that only remounts (scrolled back into view, or shifted by older messages), from\n * `body`; it focuses without scrolling, so restoring the focus never moves the list (the scroll offset is\n * the consumer's to change).\n * - `null`: not now. If the focus is already inside the target (for example on a link in its body), nothing\n * changes; if it is anywhere else, the user moved away and the intent is cleared, pending or passive.\n */\n const mayTakeFocus = React.useCallback(\n (requestId: number, target: Element | null): 'request' | 'restore' | null => {\n const active = document.activeElement;\n const isPending = requestId > fulfilledRequestId.current;\n if (!active || active === document.body) return isPending ? 'request' : 'restore';\n if (isPending && active === requestOrigin.current) return 'request';\n if (!target?.contains(active)) trackFocusResetter();\n return null;\n },\n [trackFocusResetter],\n );\n\n const markFulfilled = React.useCallback((requestId: number) => {\n fulfilledRequestId.current = Math.max(fulfilledRequestId.current, requestId);\n }, []);\n\n const listenersCleanup = React.useRef<(() => void) | null>(null);\n /**\n * Ref callback for the list wrapper. It subscribes, in the commit phase like the focus itself, to what no React\n * handler on the list sees:\n * - a `focusin` anywhere outside the list clears the intent, even when the focus was on the page and no event\n * crossed the list (for example a message scrolled away, then the composer focused and blurred);\n * - the user scrolling the list by hand (wheel, touch, scrollbar) cancels a pending request, so a target that\n * mounts much later never takes the focus, from the element the request was made from or from `body`.\n */\n const listRootRef = React.useCallback(\n (node: HTMLElement | null) => {\n listenersCleanup.current?.();\n listenersCleanup.current = null;\n if (!node) return;\n const onDocumentFocusIn = (e: FocusEvent) => {\n /** `composedPath` sees the real target when the chat is rendered inside a shadow root. */\n const target = (e.composedPath()[0] ?? e.target) as Node;\n if (!virtualListRef.current?.contains(target)) trackFocusResetter();\n };\n /** Clears a pending request (not a passive intent): marking it fulfilled would still let it restore from `body`. */\n const endPendingRequest = () => {\n if (lastRequestId.current > fulfilledRequestId.current) trackFocusResetter();\n };\n const userScrollEvents = ['wheel', 'touchstart', 'pointerdown'] as const;\n document.addEventListener('focusin', onDocumentFocusIn);\n userScrollEvents.forEach((type) => node.addEventListener(type, endPendingRequest, { passive: true }));\n listenersCleanup.current = () => {\n document.removeEventListener('focusin', onDocumentFocusIn);\n userScrollEvents.forEach((type) => node.removeEventListener(type, endPendingRequest));\n };\n },\n [virtualListRef, trackFocusResetter],\n );\n\n /** `focusout` on the list wrapper: clears the intent when the focus leaves the message list or goes to the page. */\n const handleFocusOut = React.useCallback(\n (e: React.FocusEvent<HTMLElement>) => {\n const related = e.relatedTarget as Node | null;\n if (related && e.currentTarget.contains(related)) return;\n /** Switching to another window blurs the message without the user moving the focus in the page. */\n if (!related && !document.hasFocus()) return;\n trackFocusResetter();\n },\n [trackFocusResetter],\n );\n\n return React.useMemo(\n () => ({\n focusRequest,\n trackFocusMessage,\n trackFocusResetter,\n trackFocusedMessage,\n mayTakeFocus,\n markFulfilled,\n handleFocusOut,\n listRootRef,\n }),\n [\n focusRequest,\n trackFocusMessage,\n trackFocusResetter,\n trackFocusedMessage,\n mayTakeFocus,\n markFulfilled,\n handleFocusOut,\n listRootRef,\n ],\n );\n};\n", "import * as React from 'react';\nexport { React };\n"],
5
+ "mappings": "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",
6
+ "names": ["React"]
7
+ }