@elliemae/ds-chat-container 3.80.0-next.30 → 3.80.0-next.35
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.
- package/dist/cjs/DSChatContainerDefinitions.js +7 -1
- package/dist/cjs/DSChatContainerDefinitions.js.map +2 -2
- package/dist/cjs/parts/chat-container-messages-list/ChatContainerMessagesList.js +134 -71
- package/dist/cjs/parts/chat-container-messages-list/ChatContainerMessagesList.js.map +2 -2
- package/dist/cjs/parts/chat-container-messages-list/styled.js +33 -43
- package/dist/cjs/parts/chat-container-messages-list/styled.js.map +2 -2
- package/dist/cjs/parts/chat-container-messages-list/useActionRef.js +3 -9
- package/dist/cjs/parts/chat-container-messages-list/useActionRef.js.map +2 -2
- package/dist/cjs/parts/chat-container-messages-list/useChatContainerMessageList.js +30 -22
- package/dist/cjs/parts/chat-container-messages-list/useChatContainerMessageList.js.map +2 -2
- package/dist/cjs/parts/chat-container-messages-list/useFocusAndScrollHelpers.js +38 -38
- package/dist/cjs/parts/chat-container-messages-list/useFocusAndScrollHelpers.js.map +2 -2
- package/dist/cjs/parts/chat-container-messages-list/useGlobalEventListener.js.map +1 -1
- package/dist/cjs/parts/chat-container-messages-list/useKeyboard.js +8 -15
- package/dist/cjs/parts/chat-container-messages-list/useKeyboard.js.map +2 -2
- package/dist/cjs/parts/chat-container-messages-list/useMessageFocusTracker.js +132 -0
- package/dist/cjs/parts/chat-container-messages-list/useMessageFocusTracker.js.map +7 -0
- package/dist/cjs/react-desc-prop-types.js +39 -23
- package/dist/cjs/react-desc-prop-types.js.map +2 -2
- package/dist/cjs/typescript-testing/typescript-chat-container-valid.js +31 -0
- package/dist/cjs/typescript-testing/typescript-chat-container-valid.js.map +2 -2
- package/dist/esm/DSChatContainerDefinitions.js +7 -1
- package/dist/esm/DSChatContainerDefinitions.js.map +2 -2
- package/dist/esm/parts/chat-container-messages-list/ChatContainerMessagesList.js +135 -74
- package/dist/esm/parts/chat-container-messages-list/ChatContainerMessagesList.js.map +2 -2
- package/dist/esm/parts/chat-container-messages-list/styled.js +33 -43
- package/dist/esm/parts/chat-container-messages-list/styled.js.map +2 -2
- package/dist/esm/parts/chat-container-messages-list/useActionRef.js +3 -9
- package/dist/esm/parts/chat-container-messages-list/useActionRef.js.map +2 -2
- package/dist/esm/parts/chat-container-messages-list/useChatContainerMessageList.js +30 -22
- package/dist/esm/parts/chat-container-messages-list/useChatContainerMessageList.js.map +2 -2
- package/dist/esm/parts/chat-container-messages-list/useFocusAndScrollHelpers.js +39 -39
- package/dist/esm/parts/chat-container-messages-list/useFocusAndScrollHelpers.js.map +2 -2
- package/dist/esm/parts/chat-container-messages-list/useGlobalEventListener.js.map +1 -1
- package/dist/esm/parts/chat-container-messages-list/useKeyboard.js +8 -15
- package/dist/esm/parts/chat-container-messages-list/useKeyboard.js.map +2 -2
- package/dist/esm/parts/chat-container-messages-list/useMessageFocusTracker.js +102 -0
- package/dist/esm/parts/chat-container-messages-list/useMessageFocusTracker.js.map +7 -0
- package/dist/esm/react-desc-prop-types.js +40 -24
- package/dist/esm/react-desc-prop-types.js.map +2 -2
- package/dist/esm/typescript-testing/typescript-chat-container-valid.js +31 -0
- package/dist/esm/typescript-testing/typescript-chat-container-valid.js.map +2 -2
- package/dist/types/DSChatContainerDefinitions.d.ts +8 -0
- package/dist/types/parts/chat-container-messages-list/styled.d.ts +18 -5
- package/dist/types/parts/chat-container-messages-list/useActionRef.d.ts +4 -6
- package/dist/types/parts/chat-container-messages-list/useChatContainerMessageList.d.ts +7 -2
- package/dist/types/parts/chat-container-messages-list/useFocusAndScrollHelpers.d.ts +9 -5
- package/dist/types/parts/chat-container-messages-list/useGlobalEventListener.d.ts +1 -1
- package/dist/types/parts/chat-container-messages-list/useKeyboard.d.ts +7 -3
- package/dist/types/parts/chat-container-messages-list/useMessageFocusTracker.d.ts +52 -0
- package/dist/types/react-desc-prop-types.d.ts +798 -793
- package/package.json +16 -16
|
@@ -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 { useTheme } from 'styled-components';\nimport {\n useMemoMergePropsWithDefault,\n useGetGlobalAttributes,\n useValidateTypescriptPropTypes,\n} from '@elliemae/ds-props-helpers';\nimport {
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;ADGvB,mBAAkB;AAClB,+BAAyB;AACzB,8BAIO;
|
|
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,36 +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
|
-
|
|
39
|
+
propsWithDefault: { onScrollEnds, messages },
|
|
40
|
+
fastListActionRef,
|
|
40
41
|
pendingOnScroll,
|
|
41
|
-
|
|
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
|
-
const
|
|
49
|
-
if (
|
|
61
|
+
const visibleItems = fastListActionRef.current.getVisibleItems?.() ?? [];
|
|
62
|
+
if (visibleItems.length === 0) return [];
|
|
50
63
|
pendingOnScroll.current = false;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}, [calculateRange, pendingOnScroll]);
|
|
55
|
-
const visibleItemsCache = import_react.default.useRef([]);
|
|
56
|
-
const updateCache = import_react.default.useMemo(
|
|
57
|
-
() => (0, import_lodash_es.throttle)(
|
|
58
|
-
() => {
|
|
59
|
-
visibleItemsCache.current = computeVisibleItems();
|
|
60
|
-
},
|
|
61
|
-
500,
|
|
62
|
-
{ leading: true }
|
|
63
|
-
),
|
|
64
|
-
[computeVisibleItems]
|
|
65
|
-
);
|
|
66
|
-
const getMessagesInView = import_react.default.useCallback(() => [...visibleItemsCache.current], []);
|
|
64
|
+
return visibleItems.map(String);
|
|
65
|
+
}, [fastListActionRef, pendingOnScroll]);
|
|
66
|
+
const getMessagesInView = computeVisibleItems;
|
|
67
67
|
const onScrollStop = import_react.default.useMemo(
|
|
68
68
|
() => (0, import_lodash_es.debounce)(() => {
|
|
69
69
|
const indexList = computeVisibleItems();
|
|
@@ -74,39 +74,39 @@ const useFocusAndScrollHelpers = ({
|
|
|
74
74
|
);
|
|
75
75
|
const onlyFocus = import_react.default.useCallback(
|
|
76
76
|
(index) => {
|
|
77
|
-
|
|
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 });
|
|
78
82
|
},
|
|
79
|
-
[
|
|
83
|
+
[messages, fastListActionRef, trackFocusMessage]
|
|
80
84
|
);
|
|
81
85
|
const scrollAndFocus = import_react.default.useCallback(
|
|
82
|
-
(index) => {
|
|
83
|
-
|
|
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);
|
|
84
91
|
requestAnimationFrame(() => {
|
|
85
|
-
vScroll(index,
|
|
92
|
+
vScroll(index, opts);
|
|
86
93
|
});
|
|
87
|
-
setTimeout(() => {
|
|
88
|
-
onlyFocus(index.toString());
|
|
89
|
-
}, 300);
|
|
90
|
-
updateCache();
|
|
91
94
|
},
|
|
92
|
-
[
|
|
95
|
+
[messages, trackFocusMessage, vScroll]
|
|
93
96
|
);
|
|
94
97
|
const checkPendingOnScroll = import_react.default.useCallback(() => {
|
|
95
|
-
if (pendingOnScroll.current === true)
|
|
96
|
-
|
|
97
|
-
onScrollStop();
|
|
98
|
-
}
|
|
99
|
-
}, [updateCache, onScrollStop, pendingOnScroll]);
|
|
98
|
+
if (pendingOnScroll.current === true) onScrollStop();
|
|
99
|
+
}, [onScrollStop, pendingOnScroll]);
|
|
100
100
|
return import_react.default.useMemo(
|
|
101
101
|
() => ({
|
|
102
|
+
vScroll,
|
|
102
103
|
onScrollStop,
|
|
103
|
-
updateCache,
|
|
104
104
|
getMessagesInView,
|
|
105
105
|
onlyFocus,
|
|
106
106
|
scrollAndFocus,
|
|
107
107
|
checkPendingOnScroll
|
|
108
108
|
}),
|
|
109
|
-
[
|
|
109
|
+
[vScroll, onScrollStop, getMessagesInView, onlyFocus, scrollAndFocus, checkPendingOnScroll]
|
|
110
110
|
);
|
|
111
111
|
};
|
|
112
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 { debounce
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;ADGvB,mBAAkB;AAClB,
|
|
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 |
|
|
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
|
-
(
|
|
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
|
-
(
|
|
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
|
|
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
|
-
|
|
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 |
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;ADGvB,mBAAkB;
|
|
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
|
+
}
|