@elliemae/ds-chat 2.0.5 → 2.0.6
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/cjs/ChatDataTestids.js +2 -1
- package/cjs/parts/chat-composer/ChatContainerComposer.js +12 -6
- package/cjs/parts/chat-composer/props.js +2 -0
- package/cjs/parts/chat-container/ChatContainer.js +25 -21
- package/cjs/parts/chat-container/styled.js +1 -1
- package/cjs/parts/chat-container-messages-list/ChatContainerMessagesList.js +64 -34
- package/esm/ChatDataTestids.js +2 -1
- package/esm/parts/chat-composer/ChatContainerComposer.js +12 -6
- package/esm/parts/chat-composer/props.js +2 -0
- package/esm/parts/chat-container/ChatContainer.js +25 -21
- package/esm/parts/chat-container/styled.js +1 -1
- package/esm/parts/chat-container-messages-list/ChatContainerMessagesList.js +64 -34
- package/package.json +10 -10
- package/types/ChatDataTestids.d.ts +1 -0
- package/types/parts/chat-composer/ChatContainerComposer.d.ts +3 -0
- package/types/tests/utils.d.ts +1 -27
package/cjs/ChatDataTestids.js
CHANGED
|
@@ -21,7 +21,8 @@ const ChatDataTestIds = {
|
|
|
21
21
|
SYSTEM_MSG_TIME: 'chat-system-message-time',
|
|
22
22
|
BUBBLE: 'ds-chat-bubble',
|
|
23
23
|
BUBBLE_TITLE: 'chat-bubble-title',
|
|
24
|
-
BUBBLE_BODY: 'chat-bubble-body'
|
|
24
|
+
BUBBLE_BODY: 'chat-bubble-body',
|
|
25
|
+
BANNER: 'ds-banner-container'
|
|
25
26
|
};
|
|
26
27
|
|
|
27
28
|
exports.ChatDataTestIds = ChatDataTestIds;
|
|
@@ -48,10 +48,12 @@ const ChatComposer = props => {
|
|
|
48
48
|
placeholder,
|
|
49
49
|
inputMaxLength,
|
|
50
50
|
maxLengthMessage,
|
|
51
|
+
ariaMaxLengthMessage,
|
|
51
52
|
buttonDisabled,
|
|
52
53
|
dataTestid,
|
|
53
54
|
actionRef
|
|
54
55
|
} = props;
|
|
56
|
+
const COMPOSER_MIN_HEIGHT = 32;
|
|
55
57
|
const input = React__default["default"].useRef();
|
|
56
58
|
const inputSize = React__default["default"].useRef();
|
|
57
59
|
const inputSend = React__default["default"].useRef();
|
|
@@ -62,10 +64,10 @@ const ChatComposer = props => {
|
|
|
62
64
|
var _inputSize$current, _inputSize$current2, _inputSize$current3, _inputSize$current4;
|
|
63
65
|
|
|
64
66
|
const newHeight = ((_inputSize$current = inputSize.current) === null || _inputSize$current === void 0 ? void 0 : _inputSize$current.scrollHeight) > internalMaxHeight ? internalMaxHeight : (_inputSize$current2 = inputSize.current) === null || _inputSize$current2 === void 0 ? void 0 : _inputSize$current2.scrollHeight;
|
|
65
|
-
onResize(newHeight);
|
|
67
|
+
if (newHeight !== height && newHeight > COMPOSER_MIN_HEIGHT) onResize(newHeight);
|
|
66
68
|
setHeight(newHeight);
|
|
67
69
|
setShowScroll(((_inputSize$current3 = inputSize.current) === null || _inputSize$current3 === void 0 ? void 0 : _inputSize$current3.scrollHeight) - ((_inputSize$current4 = inputSize.current) === null || _inputSize$current4 === void 0 ? void 0 : _inputSize$current4.clientHeight) > 5);
|
|
68
|
-
}, [inputValue, internalMaxHeight, onResize]);
|
|
70
|
+
}, [inputValue, internalMaxHeight, height, onResize]);
|
|
69
71
|
React__default["default"].useEffect(() => {
|
|
70
72
|
if (actionRef && actionRef.current) {
|
|
71
73
|
actionRef.current.focusToComposer = () => {
|
|
@@ -92,6 +94,9 @@ const ChatComposer = props => {
|
|
|
92
94
|
"data-testid": dataTestid
|
|
93
95
|
}, void 0, /*#__PURE__*/_jsx__default["default"](dsGrid.Grid, {
|
|
94
96
|
cols: ['1fr', '24px'],
|
|
97
|
+
style: {
|
|
98
|
+
height: heightProps.height
|
|
99
|
+
},
|
|
95
100
|
justifyContent: "center",
|
|
96
101
|
alignItems: "flex-end",
|
|
97
102
|
gutter: "xxs"
|
|
@@ -105,7 +110,9 @@ const ChatComposer = props => {
|
|
|
105
110
|
onKeyDown: onKeyDown,
|
|
106
111
|
placeholder: placeholder,
|
|
107
112
|
"aria-label": "Message to send",
|
|
108
|
-
"data-testid": "chat-composer-textarea"
|
|
113
|
+
"data-testid": "chat-composer-textarea",
|
|
114
|
+
name: "chat-composer-textarea",
|
|
115
|
+
id: "chat-composer-textarea"
|
|
109
116
|
}), /*#__PURE__*/_jsx__default["default"](dsGrid.Grid, {
|
|
110
117
|
height: "32px",
|
|
111
118
|
alignItems: "center"
|
|
@@ -132,8 +139,7 @@ const ChatComposer = props => {
|
|
|
132
139
|
width: (_input$current = input.current) === null || _input$current === void 0 ? void 0 : _input$current.clientWidth
|
|
133
140
|
},
|
|
134
141
|
onChange: handleChange,
|
|
135
|
-
value: inputValue
|
|
136
|
-
|
|
142
|
+
value: inputValue
|
|
137
143
|
}), /*#__PURE__*/_jsx__default["default"](dsGrid.Grid, {
|
|
138
144
|
cols: ['1fr', '24px'],
|
|
139
145
|
justifyContent: "flex-end",
|
|
@@ -146,7 +152,7 @@ const ChatComposer = props => {
|
|
|
146
152
|
}, void 0, inputMaxLength && hasError ? /*#__PURE__*/jsxRuntime.jsxs(jsxRuntime.Fragment, {
|
|
147
153
|
children: [/*#__PURE__*/_jsx__default["default"](styled.StyledCounterErrorA11y, {
|
|
148
154
|
role: "alert"
|
|
149
|
-
}, void 0,
|
|
155
|
+
}, void 0, ariaMaxLengthMessage || "You have entered ".concat(inputLength, " characters and have exceeded the limit by ").concat(inputLength - inputMaxLength, ". Please limit to less than ").concat(inputMaxLength, " characters. ")), maxLengthMessage || "Please limit to less than ".concat(inputMaxLength, " characters. ")]
|
|
150
156
|
}) : '', inputMaxLength ? " ".concat(inputLength, " / ").concat(inputMaxLength) : ''), _Grid || (_Grid = /*#__PURE__*/_jsx__default["default"](dsGrid.Grid, {}))));
|
|
151
157
|
};
|
|
152
158
|
|
|
@@ -8,6 +8,7 @@ var lodash = require('lodash');
|
|
|
8
8
|
const defaultComposerPros = {
|
|
9
9
|
placeholder: 'Type your message',
|
|
10
10
|
inputValue: '',
|
|
11
|
+
ariaMaxLengthMessage: '',
|
|
11
12
|
onChange: lodash.noop,
|
|
12
13
|
onResize: lodash.noop,
|
|
13
14
|
onSend: lodash.noop,
|
|
@@ -24,6 +25,7 @@ const defaultComposerPros = {
|
|
|
24
25
|
const composerProps = {
|
|
25
26
|
placeholder: reactDesc.PropTypes.string.description('Input Placeholder').defaultValue(defaultComposerPros.placeholder),
|
|
26
27
|
inputValue: reactDesc.PropTypes.string.description('Input value').defaultValue(defaultComposerPros.inputValue),
|
|
28
|
+
ariaMaxLengthMessage: reactDesc.PropTypes.string.description('Aria message for Max length message when exceeded').defaultValue(defaultComposerPros.maxLengthMessage),
|
|
27
29
|
maxLengthMessage: reactDesc.PropTypes.string.description('Max length message').defaultValue(defaultComposerPros.maxLengthMessage),
|
|
28
30
|
onChange: reactDesc.PropTypes.func.description('Called when the user types').defaultValue(defaultComposerPros.onChange),
|
|
29
31
|
onResize: reactDesc.PropTypes.func.description('Called the input changes his height').defaultValue(defaultComposerPros.onResize),
|
|
@@ -29,14 +29,14 @@ var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
|
|
|
29
29
|
var DSIndeterminateProgressIndicator__default = /*#__PURE__*/_interopDefaultLegacy(DSIndeterminateProgressIndicator);
|
|
30
30
|
var DSBanner__default = /*#__PURE__*/_interopDefaultLegacy(DSBanner);
|
|
31
31
|
|
|
32
|
-
var _DSIndeterminateProgr
|
|
32
|
+
var _DSIndeterminateProgr;
|
|
33
33
|
|
|
34
34
|
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
|
|
35
35
|
|
|
36
36
|
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty__default["default"](target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
|
|
37
37
|
|
|
38
38
|
const ChatContainer = props$1 => {
|
|
39
|
-
var _containerRef$current;
|
|
39
|
+
var _messagesListProps$me2, _containerRef$current;
|
|
40
40
|
|
|
41
41
|
useValidateProps.useValidateProps(props$1, props.containerProps);
|
|
42
42
|
const {
|
|
@@ -67,18 +67,16 @@ const ChatContainer = props$1 => {
|
|
|
67
67
|
});
|
|
68
68
|
const onResize = React__default["default"].useCallback(height => {
|
|
69
69
|
if (onComposerResize) onComposerResize(height);
|
|
70
|
-
|
|
71
|
-
if (height > 30) {
|
|
72
|
-
/*
|
|
70
|
+
/*
|
|
73
71
|
setTimeout is used to equal the animation duration from the
|
|
74
72
|
composer text area when increase its size
|
|
75
73
|
*/
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
}
|
|
74
|
+
|
|
75
|
+
setTimeout(() => {
|
|
76
|
+
actionRef.current.scrollToIndex(messagesListProps.messages.length - 1, {
|
|
77
|
+
align: 'start'
|
|
78
|
+
});
|
|
79
|
+
}, 50);
|
|
82
80
|
}, [onComposerResize, messagesListProps.messages, actionRef]);
|
|
83
81
|
const Container = device === 'desktop' ? styled.StyledChatContainerContent : styled.MobileStyledChatContainerContent;
|
|
84
82
|
const handleTabs = React__default["default"].useCallback(e => {
|
|
@@ -86,17 +84,23 @@ const ChatContainer = props$1 => {
|
|
|
86
84
|
const ref = actionRef || listRef;
|
|
87
85
|
|
|
88
86
|
if (e.shiftKey && e.key === 'Tab') {
|
|
89
|
-
|
|
87
|
+
var _messagesListProps$me;
|
|
88
|
+
|
|
89
|
+
if (((_messagesListProps$me = messagesListProps.messages) === null || _messagesListProps$me === void 0 ? void 0 : _messagesListProps$me.length) > 0) {
|
|
90
|
+
e.preventDefault();
|
|
91
|
+
|
|
92
|
+
if (['top', 'bottom'].includes(bannerPosition)) {
|
|
93
|
+
bannerRef.current.focusOnWrapper();
|
|
94
|
+
} else {
|
|
95
|
+
var _ref$current;
|
|
90
96
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const last = ref.current.getMessagesInView().pop();
|
|
95
|
-
ref.current.focusToIndexWithoutScroll(last);
|
|
97
|
+
const last = ref.current.getMessagesInView().pop();
|
|
98
|
+
(_ref$current = ref.current) === null || _ref$current === void 0 ? void 0 : _ref$current.focusToIndexWithoutScroll(last);
|
|
99
|
+
}
|
|
96
100
|
}
|
|
97
101
|
}
|
|
98
102
|
}
|
|
99
|
-
}, [actionRef, onComposerKeyDown, bannerPosition]); // Map actions
|
|
103
|
+
}, [actionRef, onComposerKeyDown, bannerPosition, (_messagesListProps$me2 = messagesListProps.messages) === null || _messagesListProps$me2 === void 0 ? void 0 : _messagesListProps$me2.length]); // Map actions
|
|
100
104
|
|
|
101
105
|
React__default["default"].useEffect(() => {
|
|
102
106
|
if (actionRef && actionRef.current) {
|
|
@@ -146,15 +150,15 @@ const ChatContainer = props$1 => {
|
|
|
146
150
|
isOpen: true,
|
|
147
151
|
type: bannerProps.type || DSBanner.BANNER_TYPES.INFO,
|
|
148
152
|
actionRef: bannerRef
|
|
149
|
-
})), sendingMessages && sendingMessages.length > 0 ?
|
|
153
|
+
})), sendingMessages && sendingMessages.length > 0 ? /*#__PURE__*/_jsx__default["default"](styled.StyledIndicatorContainer, {
|
|
150
154
|
isLoading: "bottom",
|
|
151
155
|
linear: true,
|
|
152
156
|
"data-testid": "ds-chat-loading-indicator"
|
|
153
157
|
}, void 0, /*#__PURE__*/_jsx__default["default"](DSIndeterminateProgressIndicator__default["default"], {
|
|
154
158
|
processing: true,
|
|
155
|
-
title:
|
|
159
|
+
title: sendingText,
|
|
156
160
|
lineOnly: true
|
|
157
|
-
}))
|
|
161
|
+
})) : null), /*#__PURE__*/jsxRuntime.jsx(ChatContainerComposer.ChatComposer, _objectSpread(_objectSpread({}, composerProps), {}, {
|
|
158
162
|
onKeyDown: handleTabs,
|
|
159
163
|
onResize: onResize,
|
|
160
164
|
maxHeight: (((_containerRef$current = containerRef.current) === null || _containerRef$current === void 0 ? void 0 : _containerRef$current.clientHeight) || 0) / 2,
|
|
@@ -18,7 +18,7 @@ const StyledChatContainerContent = /*#__PURE__*/styled__default["default"](dsGri
|
|
|
18
18
|
})(["height:100%;min-width:", "px;overflow:hidden;background:", ";position:relative;"], CONTAINER_WIDTH, props => props.theme.colors.neutral['050']);
|
|
19
19
|
const StyledIndicatorContainer = /*#__PURE__*/styled__default["default"].div.withConfig({
|
|
20
20
|
componentId: "sc-d732wo-1"
|
|
21
|
-
})(["transition:0.4s ease-in-out;position:absolute;visibility:hidden;pointer-events:none;width:100%;padding:0 ", ";left:0;right:0;background:", ";", " ", " & div,a{font-size:13px;}"], props => !props.linear ? props.theme.space.xxs : 0, props => props.theme.colors.neutral['000'], props => props.isLoading === 'top' ? "\n top: -41px;\n transform: translate(0, 41px);\n visibility: visible;\n pointer-events: auto
|
|
21
|
+
})(["transition:0.4s ease-in-out;position:absolute;visibility:hidden;pointer-events:none;width:100%;padding:0 ", ";left:0;right:0;background:", ";", " ", " & div,a{font-size:13px;}"], props => !props.linear ? props.theme.space.xxs : 0, props => props.theme.colors.neutral['000'], props => props.isLoading === 'top' ? "\n top: -41px;\n transform: translate(0, 41px);\n visibility: visible;\n pointer-events: auto;\n " : '', props => props.isLoading === 'bottom' ? "\n bottom: -41px;\n transform: translate(0, -41px);\n visibility: visible;\n pointer-events: auto;\n " : '');
|
|
22
22
|
const MobileStyledChatContainerContent = /*#__PURE__*/styled__default["default"](StyledChatContainerContent).withConfig({
|
|
23
23
|
componentId: "sc-d732wo-2"
|
|
24
24
|
})(["", ",", "{font-size:16px;}", "{font-size:13px;}", "{background-color:#fff;box-shadow:-0 1px 3px rgba(53,60,70,0.8);}", ",", "{font-size:16px;text-align:left;}", "{font-size:13px;}", "{font-size:16px;}", "{font-size:16px;}"], styled$1.StyledTitleLeftBubble, styled$1.StyleBodyWrapper, styled$1.StyledTitleRightBubble, style.Wrapper, style.Title, style.Message, style.Time, styled$2.StyledComposerInput, StyledIndicatorContainer);
|
|
@@ -19,6 +19,7 @@ var React = require('react');
|
|
|
19
19
|
var reactDesc = require('react-desc');
|
|
20
20
|
var reactVirtual = require('react-virtual');
|
|
21
21
|
var DSButton = require('@elliemae/ds-button');
|
|
22
|
+
var lodash = require('lodash');
|
|
22
23
|
var ChatBubble = require('../bubble/ChatBubble.js');
|
|
23
24
|
var props$1 = require('../bubble/props.js');
|
|
24
25
|
var MessageDelimiter = require('../message-delimiter/MessageDelimiter.js');
|
|
@@ -68,6 +69,7 @@ const ChatContainerMessagesList = props$2 => {
|
|
|
68
69
|
isLoading,
|
|
69
70
|
bannerRef
|
|
70
71
|
} = props$2;
|
|
72
|
+
const pendingOnScroll = React__default["default"].useRef(false);
|
|
71
73
|
const loadMoreRef = React__default["default"].useRef(null);
|
|
72
74
|
const virtualListRef = React__default["default"].useRef(); // estimateSize should not be really required given what was stated on
|
|
73
75
|
// https://github.com/tannerlinsley/react-virtual/issues/23
|
|
@@ -88,9 +90,16 @@ const ChatContainerMessagesList = props$2 => {
|
|
|
88
90
|
estimateSize: React__default["default"].useCallback(() => 80, [])
|
|
89
91
|
}));
|
|
90
92
|
const newMessages = useGetNewMessages.useGetNewMessages(messages);
|
|
91
|
-
const
|
|
93
|
+
const debouncedScroll = React__default["default"].useMemo(() => lodash.debounce(() => {
|
|
92
94
|
var _virtualListRef$curre;
|
|
93
95
|
|
|
96
|
+
if (document.visibilityState === 'hidden') {
|
|
97
|
+
pendingOnScroll.current = true;
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!virtualListRef.current) return [];
|
|
102
|
+
pendingOnScroll.current = false;
|
|
94
103
|
const {
|
|
95
104
|
bottom,
|
|
96
105
|
top
|
|
@@ -99,15 +108,15 @@ const ChatContainerMessagesList = props$2 => {
|
|
|
99
108
|
const indexList = [];
|
|
100
109
|
list.forEach(item => {
|
|
101
110
|
const {
|
|
102
|
-
top: itemTop
|
|
103
|
-
bottom: itemBottom
|
|
111
|
+
top: itemTop
|
|
104
112
|
} = item.getBoundingClientRect();
|
|
105
113
|
const itemPadding = 10;
|
|
106
|
-
if (itemTop + itemPadding < bottom &&
|
|
114
|
+
if (itemTop + itemPadding < bottom && itemTop + itemPadding > top) indexList.push(item.dataset.index);
|
|
107
115
|
});
|
|
108
116
|
if (onScrollEnds) onScrollEnds(indexList);
|
|
109
117
|
return indexList;
|
|
110
|
-
}, [onScrollEnds]);
|
|
118
|
+
}, 500), [onScrollEnds]);
|
|
119
|
+
const onScrollStop = React__default["default"].useCallback(debouncedScroll, [debouncedScroll]);
|
|
111
120
|
const onlyFocus = React__default["default"].useCallback(index => {
|
|
112
121
|
setTimeout(() => {
|
|
113
122
|
virtualListRef.current.querySelector("[data-index=\"".concat(index, "\"]")).focus();
|
|
@@ -119,7 +128,8 @@ const ChatContainerMessagesList = props$2 => {
|
|
|
119
128
|
};
|
|
120
129
|
vScroll(index, b);
|
|
121
130
|
onlyFocus(index);
|
|
122
|
-
|
|
131
|
+
onScrollStop();
|
|
132
|
+
}, [onScrollStop, onlyFocus, vScroll]);
|
|
123
133
|
React__default["default"].useEffect(() => {
|
|
124
134
|
if (actionRef && actionRef.current) {
|
|
125
135
|
actionRef.current.scrollToIndex = function (a) {
|
|
@@ -127,9 +137,11 @@ const ChatContainerMessagesList = props$2 => {
|
|
|
127
137
|
align: 'center'
|
|
128
138
|
};
|
|
129
139
|
vScroll(a, b);
|
|
140
|
+
onScrollStop();
|
|
130
141
|
};
|
|
131
142
|
|
|
132
143
|
actionRef.current.getMessagesInView = onScrollStop;
|
|
144
|
+
onScrollStop(); // init useMemo due to debounce use.
|
|
133
145
|
|
|
134
146
|
actionRef.current.focusToIndex = function (a) {
|
|
135
147
|
let b = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
|
|
@@ -140,7 +152,7 @@ const ChatContainerMessagesList = props$2 => {
|
|
|
140
152
|
|
|
141
153
|
actionRef.current.focusToIndexWithoutScroll = onlyFocus;
|
|
142
154
|
|
|
143
|
-
actionRef.current.focusToList =
|
|
155
|
+
actionRef.current.focusToList = () => {
|
|
144
156
|
var _virtualListRef$curre2;
|
|
145
157
|
|
|
146
158
|
(_virtualListRef$curre2 = virtualListRef.current) === null || _virtualListRef$curre2 === void 0 ? void 0 : _virtualListRef$curre2.focus();
|
|
@@ -157,7 +169,17 @@ const ChatContainerMessagesList = props$2 => {
|
|
|
157
169
|
switch (key) {
|
|
158
170
|
case 'ArrowUp':
|
|
159
171
|
e.preventDefault();
|
|
160
|
-
|
|
172
|
+
|
|
173
|
+
if (index > 0) {
|
|
174
|
+
var _virtualListRef$curre3;
|
|
175
|
+
|
|
176
|
+
((_virtualListRef$curre3 = virtualListRef.current) === null || _virtualListRef$curre3 === void 0 ? void 0 : _virtualListRef$curre3.querySelector("[data-index=\"".concat(index - 1, "\"]"))).focus();
|
|
177
|
+
} else if (hasMoreItems === 'top') {
|
|
178
|
+
var _loadMoreRef$current;
|
|
179
|
+
|
|
180
|
+
(_loadMoreRef$current = loadMoreRef.current) === null || _loadMoreRef$current === void 0 ? void 0 : _loadMoreRef$current.focus();
|
|
181
|
+
}
|
|
182
|
+
|
|
161
183
|
break;
|
|
162
184
|
|
|
163
185
|
case 'ArrowDown':
|
|
@@ -180,31 +202,31 @@ const ChatContainerMessagesList = props$2 => {
|
|
|
180
202
|
break;
|
|
181
203
|
|
|
182
204
|
case 'Tab':
|
|
183
|
-
e.preventDefault();
|
|
184
|
-
|
|
185
205
|
if (hasMoreItems === 'bottom' && !e.shiftKey) {
|
|
186
|
-
var _loadMoreRef$current;
|
|
187
|
-
|
|
188
|
-
(_loadMoreRef$current = loadMoreRef.current) === null || _loadMoreRef$current === void 0 ? void 0 : _loadMoreRef$current.focus();
|
|
189
|
-
} else if (hasMoreItems === 'top' && e.shiftKey) {
|
|
190
206
|
var _loadMoreRef$current2;
|
|
191
207
|
|
|
208
|
+
e.preventDefault();
|
|
192
209
|
(_loadMoreRef$current2 = loadMoreRef.current) === null || _loadMoreRef$current2 === void 0 ? void 0 : _loadMoreRef$current2.focus();
|
|
210
|
+
} else if (hasMoreItems === 'top' && e.shiftKey) {
|
|
211
|
+
var _loadMoreRef$current3;
|
|
212
|
+
|
|
213
|
+
e.preventDefault();
|
|
214
|
+
(_loadMoreRef$current3 = loadMoreRef.current) === null || _loadMoreRef$current3 === void 0 ? void 0 : _loadMoreRef$current3.focus();
|
|
193
215
|
} else if (!e.shiftKey) {
|
|
194
216
|
if (bannerPosition) {
|
|
217
|
+
e.preventDefault();
|
|
195
218
|
bannerRef.current.focusOnWrapper();
|
|
196
|
-
} else {
|
|
197
|
-
actionRef.current.focusToComposer();
|
|
198
219
|
}
|
|
199
220
|
} else {
|
|
200
|
-
var _virtualListRef$
|
|
221
|
+
var _virtualListRef$curre4;
|
|
201
222
|
|
|
202
|
-
|
|
223
|
+
e.preventDefault();
|
|
224
|
+
(_virtualListRef$curre4 = virtualListRef.current) === null || _virtualListRef$curre4 === void 0 ? void 0 : _virtualListRef$curre4.focus();
|
|
203
225
|
}
|
|
204
226
|
|
|
205
227
|
break;
|
|
206
228
|
}
|
|
207
|
-
}, [
|
|
229
|
+
}, [bannerRef, hasMoreItems, messages.length, bannerPosition, scrollAndFocus]);
|
|
208
230
|
const handleListKey = React__default["default"].useCallback(e => {
|
|
209
231
|
const {
|
|
210
232
|
key
|
|
@@ -213,12 +235,22 @@ const ChatContainerMessagesList = props$2 => {
|
|
|
213
235
|
switch (key) {
|
|
214
236
|
case 'ArrowUp':
|
|
215
237
|
e.preventDefault();
|
|
216
|
-
|
|
238
|
+
|
|
239
|
+
if (document.activeElement !== loadMoreRef.current) {
|
|
240
|
+
onlyFocus(onScrollStop().shift());
|
|
241
|
+
}
|
|
242
|
+
|
|
217
243
|
break;
|
|
218
244
|
|
|
219
245
|
case 'ArrowDown':
|
|
220
246
|
e.preventDefault();
|
|
221
|
-
|
|
247
|
+
|
|
248
|
+
if (document.activeElement === loadMoreRef.current) {
|
|
249
|
+
virtualListRef.current.querySelector("[data-index=\"0\"]").focus();
|
|
250
|
+
} else {
|
|
251
|
+
onlyFocus(onScrollStop().pop());
|
|
252
|
+
}
|
|
253
|
+
|
|
222
254
|
break;
|
|
223
255
|
|
|
224
256
|
case 'Home':
|
|
@@ -234,11 +266,6 @@ const ChatContainerMessagesList = props$2 => {
|
|
|
234
266
|
case 'Tab':
|
|
235
267
|
if (document.activeElement !== loadMoreRef.current) {
|
|
236
268
|
if (hasMoreItems === 'bottom' && !e.shiftKey) {
|
|
237
|
-
var _loadMoreRef$current3;
|
|
238
|
-
|
|
239
|
-
e.preventDefault();
|
|
240
|
-
(_loadMoreRef$current3 = loadMoreRef.current) === null || _loadMoreRef$current3 === void 0 ? void 0 : _loadMoreRef$current3.focus();
|
|
241
|
-
} else if (hasMoreItems === 'top' && e.shiftKey) {
|
|
242
269
|
var _loadMoreRef$current4;
|
|
243
270
|
|
|
244
271
|
e.preventDefault();
|
|
@@ -251,15 +278,18 @@ const ChatContainerMessagesList = props$2 => {
|
|
|
251
278
|
e.preventDefault();
|
|
252
279
|
actionRef.current.focusToComposer();
|
|
253
280
|
} else {
|
|
254
|
-
var _virtualListRef$
|
|
281
|
+
var _virtualListRef$curre5;
|
|
255
282
|
|
|
256
283
|
e.preventDefault();
|
|
257
|
-
(_virtualListRef$
|
|
284
|
+
(_virtualListRef$curre5 = virtualListRef.current) === null || _virtualListRef$curre5 === void 0 ? void 0 : _virtualListRef$curre5.focus();
|
|
258
285
|
}
|
|
259
286
|
|
|
260
287
|
break;
|
|
261
288
|
}
|
|
262
289
|
}, [actionRef, hasMoreItems, messages.length, onScrollStop, onlyFocus, scrollAndFocus]);
|
|
290
|
+
const checkPendingOnScroll = React__default["default"].useCallback(() => {
|
|
291
|
+
if (pendingOnScroll.current === true) onScrollStop();
|
|
292
|
+
}, [onScrollStop]);
|
|
263
293
|
React__default["default"].useEffect(() => {
|
|
264
294
|
let handlerCallback = null;
|
|
265
295
|
const localVirtualRef = virtualListRef.current;
|
|
@@ -273,11 +303,13 @@ const ChatContainerMessagesList = props$2 => {
|
|
|
273
303
|
virtualListRef.current.addEventListener('scroll', onScroll);
|
|
274
304
|
}
|
|
275
305
|
|
|
306
|
+
document.addEventListener('visibilitychange', checkPendingOnScroll);
|
|
276
307
|
return () => {
|
|
277
308
|
if (handlerCallback) clearTimeout(handlerCallback);
|
|
278
309
|
if (onScrollEnds) localVirtualRef.removeEventListener('scroll', onScroll);
|
|
310
|
+
document.removeEventListener('visibilitychange', checkPendingOnScroll);
|
|
279
311
|
};
|
|
280
|
-
}, [onScrollStop, onScrollEnds]);
|
|
312
|
+
}, [onScrollStop, onScrollEnds, checkPendingOnScroll]);
|
|
281
313
|
const render = React__default["default"].useMemo(() => virtualItems.map(virtualItem => {
|
|
282
314
|
const {
|
|
283
315
|
index,
|
|
@@ -298,7 +330,7 @@ const ChatContainerMessagesList = props$2 => {
|
|
|
298
330
|
"data-index": index,
|
|
299
331
|
style: style,
|
|
300
332
|
ref: measureRef,
|
|
301
|
-
tabIndex:
|
|
333
|
+
tabIndex: -1,
|
|
302
334
|
onKeyDown: handleKey,
|
|
303
335
|
role: "listitem",
|
|
304
336
|
children: /*#__PURE__*/_jsx__default["default"](styled.StyledWrapperListItem, {
|
|
@@ -314,14 +346,12 @@ const ChatContainerMessagesList = props$2 => {
|
|
|
314
346
|
return /*#__PURE__*/_jsx__default["default"](styled.StyledWrapper, {}, void 0, /*#__PURE__*/jsxRuntime.jsx(styled.StyledChatContainerContentList, {
|
|
315
347
|
"data-testid": "chat-thread-container",
|
|
316
348
|
tabIndex: 0,
|
|
317
|
-
"aria-live": "polite",
|
|
318
|
-
"aria-relevant": "additions",
|
|
319
349
|
ref: virtualListRef,
|
|
320
350
|
role: "list",
|
|
321
351
|
onKeyDown: handleListKey,
|
|
322
352
|
children: /*#__PURE__*/_jsx__default["default"](styled.StyledChatContainerContentListScroll, {
|
|
323
353
|
height: "".concat(totalSize, "px")
|
|
324
|
-
}, void 0,
|
|
354
|
+
}, void 0, !isLoading && hasMoreItems ? /*#__PURE__*/_jsx__default["default"](styled.StyledLoadMoreContainer, {
|
|
325
355
|
hasMoreItems: hasMoreItems
|
|
326
356
|
}, void 0, /*#__PURE__*/_jsx__default["default"](DSButton__default["default"], {
|
|
327
357
|
labelText: "Load More",
|
|
@@ -329,7 +359,7 @@ const ChatContainerMessagesList = props$2 => {
|
|
|
329
359
|
onClick: getMoreMessages,
|
|
330
360
|
"data-testid": "ds-chat-load-more-btn",
|
|
331
361
|
innerRef: loadMoreRef
|
|
332
|
-
})) : null)
|
|
362
|
+
})) : null, render)
|
|
333
363
|
}), /*#__PURE__*/_jsx__default["default"](styled.StyledNewMessagesHiddenContainer, {
|
|
334
364
|
"aria-live": "polite",
|
|
335
365
|
"aria-hidden": "false"
|
package/esm/ChatDataTestids.js
CHANGED
|
@@ -17,7 +17,8 @@ const ChatDataTestIds = {
|
|
|
17
17
|
SYSTEM_MSG_TIME: 'chat-system-message-time',
|
|
18
18
|
BUBBLE: 'ds-chat-bubble',
|
|
19
19
|
BUBBLE_TITLE: 'chat-bubble-title',
|
|
20
|
-
BUBBLE_BODY: 'chat-bubble-body'
|
|
20
|
+
BUBBLE_BODY: 'chat-bubble-body',
|
|
21
|
+
BANNER: 'ds-banner-container'
|
|
21
22
|
};
|
|
22
23
|
|
|
23
24
|
export { ChatDataTestIds };
|
|
@@ -38,10 +38,12 @@ const ChatComposer = props => {
|
|
|
38
38
|
placeholder,
|
|
39
39
|
inputMaxLength,
|
|
40
40
|
maxLengthMessage,
|
|
41
|
+
ariaMaxLengthMessage,
|
|
41
42
|
buttonDisabled,
|
|
42
43
|
dataTestid,
|
|
43
44
|
actionRef
|
|
44
45
|
} = props;
|
|
46
|
+
const COMPOSER_MIN_HEIGHT = 32;
|
|
45
47
|
const input = React.useRef();
|
|
46
48
|
const inputSize = React.useRef();
|
|
47
49
|
const inputSend = React.useRef();
|
|
@@ -52,10 +54,10 @@ const ChatComposer = props => {
|
|
|
52
54
|
var _inputSize$current, _inputSize$current2, _inputSize$current3, _inputSize$current4;
|
|
53
55
|
|
|
54
56
|
const newHeight = ((_inputSize$current = inputSize.current) === null || _inputSize$current === void 0 ? void 0 : _inputSize$current.scrollHeight) > internalMaxHeight ? internalMaxHeight : (_inputSize$current2 = inputSize.current) === null || _inputSize$current2 === void 0 ? void 0 : _inputSize$current2.scrollHeight;
|
|
55
|
-
onResize(newHeight);
|
|
57
|
+
if (newHeight !== height && newHeight > COMPOSER_MIN_HEIGHT) onResize(newHeight);
|
|
56
58
|
setHeight(newHeight);
|
|
57
59
|
setShowScroll(((_inputSize$current3 = inputSize.current) === null || _inputSize$current3 === void 0 ? void 0 : _inputSize$current3.scrollHeight) - ((_inputSize$current4 = inputSize.current) === null || _inputSize$current4 === void 0 ? void 0 : _inputSize$current4.clientHeight) > 5);
|
|
58
|
-
}, [inputValue, internalMaxHeight, onResize]);
|
|
60
|
+
}, [inputValue, internalMaxHeight, height, onResize]);
|
|
59
61
|
React.useEffect(() => {
|
|
60
62
|
if (actionRef && actionRef.current) {
|
|
61
63
|
actionRef.current.focusToComposer = () => {
|
|
@@ -82,6 +84,9 @@ const ChatComposer = props => {
|
|
|
82
84
|
"data-testid": dataTestid
|
|
83
85
|
}, void 0, /*#__PURE__*/_jsx(Grid, {
|
|
84
86
|
cols: ['1fr', '24px'],
|
|
87
|
+
style: {
|
|
88
|
+
height: heightProps.height
|
|
89
|
+
},
|
|
85
90
|
justifyContent: "center",
|
|
86
91
|
alignItems: "flex-end",
|
|
87
92
|
gutter: "xxs"
|
|
@@ -95,7 +100,9 @@ const ChatComposer = props => {
|
|
|
95
100
|
onKeyDown: onKeyDown,
|
|
96
101
|
placeholder: placeholder,
|
|
97
102
|
"aria-label": "Message to send",
|
|
98
|
-
"data-testid": "chat-composer-textarea"
|
|
103
|
+
"data-testid": "chat-composer-textarea",
|
|
104
|
+
name: "chat-composer-textarea",
|
|
105
|
+
id: "chat-composer-textarea"
|
|
99
106
|
}), /*#__PURE__*/_jsx(Grid, {
|
|
100
107
|
height: "32px",
|
|
101
108
|
alignItems: "center"
|
|
@@ -122,8 +129,7 @@ const ChatComposer = props => {
|
|
|
122
129
|
width: (_input$current = input.current) === null || _input$current === void 0 ? void 0 : _input$current.clientWidth
|
|
123
130
|
},
|
|
124
131
|
onChange: handleChange,
|
|
125
|
-
value: inputValue
|
|
126
|
-
|
|
132
|
+
value: inputValue
|
|
127
133
|
}), /*#__PURE__*/_jsx(Grid, {
|
|
128
134
|
cols: ['1fr', '24px'],
|
|
129
135
|
justifyContent: "flex-end",
|
|
@@ -136,7 +142,7 @@ const ChatComposer = props => {
|
|
|
136
142
|
}, void 0, inputMaxLength && hasError ? /*#__PURE__*/jsxs(Fragment, {
|
|
137
143
|
children: [/*#__PURE__*/_jsx(StyledCounterErrorA11y, {
|
|
138
144
|
role: "alert"
|
|
139
|
-
}, void 0,
|
|
145
|
+
}, void 0, ariaMaxLengthMessage || "You have entered ".concat(inputLength, " characters and have exceeded the limit by ").concat(inputLength - inputMaxLength, ". Please limit to less than ").concat(inputMaxLength, " characters. ")), maxLengthMessage || "Please limit to less than ".concat(inputMaxLength, " characters. ")]
|
|
140
146
|
}) : '', inputMaxLength ? " ".concat(inputLength, " / ").concat(inputMaxLength) : ''), _Grid || (_Grid = /*#__PURE__*/_jsx(Grid, {}))));
|
|
141
147
|
};
|
|
142
148
|
|
|
@@ -4,6 +4,7 @@ import { noop } from 'lodash';
|
|
|
4
4
|
const defaultComposerPros = {
|
|
5
5
|
placeholder: 'Type your message',
|
|
6
6
|
inputValue: '',
|
|
7
|
+
ariaMaxLengthMessage: '',
|
|
7
8
|
onChange: noop,
|
|
8
9
|
onResize: noop,
|
|
9
10
|
onSend: noop,
|
|
@@ -20,6 +21,7 @@ const defaultComposerPros = {
|
|
|
20
21
|
const composerProps = {
|
|
21
22
|
placeholder: PropTypes.string.description('Input Placeholder').defaultValue(defaultComposerPros.placeholder),
|
|
22
23
|
inputValue: PropTypes.string.description('Input value').defaultValue(defaultComposerPros.inputValue),
|
|
24
|
+
ariaMaxLengthMessage: PropTypes.string.description('Aria message for Max length message when exceeded').defaultValue(defaultComposerPros.maxLengthMessage),
|
|
23
25
|
maxLengthMessage: PropTypes.string.description('Max length message').defaultValue(defaultComposerPros.maxLengthMessage),
|
|
24
26
|
onChange: PropTypes.func.description('Called when the user types').defaultValue(defaultComposerPros.onChange),
|
|
25
27
|
onResize: PropTypes.func.description('Called the input changes his height').defaultValue(defaultComposerPros.onResize),
|
|
@@ -17,14 +17,14 @@ import { StyledIndicatorContainer, StyledChatContainerContent, MobileStyledChatC
|
|
|
17
17
|
import { useValidateProps } from './config/useValidateProps.js';
|
|
18
18
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
19
19
|
|
|
20
|
-
var _DSIndeterminateProgr
|
|
20
|
+
var _DSIndeterminateProgr;
|
|
21
21
|
|
|
22
22
|
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
|
|
23
23
|
|
|
24
24
|
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
|
|
25
25
|
|
|
26
26
|
const ChatContainer = props => {
|
|
27
|
-
var _containerRef$current;
|
|
27
|
+
var _messagesListProps$me2, _containerRef$current;
|
|
28
28
|
|
|
29
29
|
useValidateProps(props, containerProps);
|
|
30
30
|
const {
|
|
@@ -55,18 +55,16 @@ const ChatContainer = props => {
|
|
|
55
55
|
});
|
|
56
56
|
const onResize = React.useCallback(height => {
|
|
57
57
|
if (onComposerResize) onComposerResize(height);
|
|
58
|
-
|
|
59
|
-
if (height > 30) {
|
|
60
|
-
/*
|
|
58
|
+
/*
|
|
61
59
|
setTimeout is used to equal the animation duration from the
|
|
62
60
|
composer text area when increase its size
|
|
63
61
|
*/
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
}
|
|
62
|
+
|
|
63
|
+
setTimeout(() => {
|
|
64
|
+
actionRef.current.scrollToIndex(messagesListProps.messages.length - 1, {
|
|
65
|
+
align: 'start'
|
|
66
|
+
});
|
|
67
|
+
}, 50);
|
|
70
68
|
}, [onComposerResize, messagesListProps.messages, actionRef]);
|
|
71
69
|
const Container = device === 'desktop' ? StyledChatContainerContent : MobileStyledChatContainerContent;
|
|
72
70
|
const handleTabs = React.useCallback(e => {
|
|
@@ -74,17 +72,23 @@ const ChatContainer = props => {
|
|
|
74
72
|
const ref = actionRef || listRef;
|
|
75
73
|
|
|
76
74
|
if (e.shiftKey && e.key === 'Tab') {
|
|
77
|
-
|
|
75
|
+
var _messagesListProps$me;
|
|
76
|
+
|
|
77
|
+
if (((_messagesListProps$me = messagesListProps.messages) === null || _messagesListProps$me === void 0 ? void 0 : _messagesListProps$me.length) > 0) {
|
|
78
|
+
e.preventDefault();
|
|
79
|
+
|
|
80
|
+
if (['top', 'bottom'].includes(bannerPosition)) {
|
|
81
|
+
bannerRef.current.focusOnWrapper();
|
|
82
|
+
} else {
|
|
83
|
+
var _ref$current;
|
|
78
84
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const last = ref.current.getMessagesInView().pop();
|
|
83
|
-
ref.current.focusToIndexWithoutScroll(last);
|
|
85
|
+
const last = ref.current.getMessagesInView().pop();
|
|
86
|
+
(_ref$current = ref.current) === null || _ref$current === void 0 ? void 0 : _ref$current.focusToIndexWithoutScroll(last);
|
|
87
|
+
}
|
|
84
88
|
}
|
|
85
89
|
}
|
|
86
90
|
}
|
|
87
|
-
}, [actionRef, onComposerKeyDown, bannerPosition]); // Map actions
|
|
91
|
+
}, [actionRef, onComposerKeyDown, bannerPosition, (_messagesListProps$me2 = messagesListProps.messages) === null || _messagesListProps$me2 === void 0 ? void 0 : _messagesListProps$me2.length]); // Map actions
|
|
88
92
|
|
|
89
93
|
React.useEffect(() => {
|
|
90
94
|
if (actionRef && actionRef.current) {
|
|
@@ -134,15 +138,15 @@ const ChatContainer = props => {
|
|
|
134
138
|
isOpen: true,
|
|
135
139
|
type: bannerProps.type || BANNER_TYPES.INFO,
|
|
136
140
|
actionRef: bannerRef
|
|
137
|
-
})), sendingMessages && sendingMessages.length > 0 ?
|
|
141
|
+
})), sendingMessages && sendingMessages.length > 0 ? /*#__PURE__*/_jsx(StyledIndicatorContainer, {
|
|
138
142
|
isLoading: "bottom",
|
|
139
143
|
linear: true,
|
|
140
144
|
"data-testid": "ds-chat-loading-indicator"
|
|
141
145
|
}, void 0, /*#__PURE__*/_jsx(DSIndeterminateProgressIndicator, {
|
|
142
146
|
processing: true,
|
|
143
|
-
title:
|
|
147
|
+
title: sendingText,
|
|
144
148
|
lineOnly: true
|
|
145
|
-
}))
|
|
149
|
+
})) : null), /*#__PURE__*/jsx(ChatComposer, _objectSpread(_objectSpread({}, composerProps), {}, {
|
|
146
150
|
onKeyDown: handleTabs,
|
|
147
151
|
onResize: onResize,
|
|
148
152
|
maxHeight: (((_containerRef$current = containerRef.current) === null || _containerRef$current === void 0 ? void 0 : _containerRef$current.clientHeight) || 0) / 2,
|
|
@@ -10,7 +10,7 @@ const StyledChatContainerContent = /*#__PURE__*/styled(Grid).withConfig({
|
|
|
10
10
|
})(["height:100%;min-width:", "px;overflow:hidden;background:", ";position:relative;"], CONTAINER_WIDTH, props => props.theme.colors.neutral['050']);
|
|
11
11
|
const StyledIndicatorContainer = /*#__PURE__*/styled.div.withConfig({
|
|
12
12
|
componentId: "sc-d732wo-1"
|
|
13
|
-
})(["transition:0.4s ease-in-out;position:absolute;visibility:hidden;pointer-events:none;width:100%;padding:0 ", ";left:0;right:0;background:", ";", " ", " & div,a{font-size:13px;}"], props => !props.linear ? props.theme.space.xxs : 0, props => props.theme.colors.neutral['000'], props => props.isLoading === 'top' ? "\n top: -41px;\n transform: translate(0, 41px);\n visibility: visible;\n pointer-events: auto
|
|
13
|
+
})(["transition:0.4s ease-in-out;position:absolute;visibility:hidden;pointer-events:none;width:100%;padding:0 ", ";left:0;right:0;background:", ";", " ", " & div,a{font-size:13px;}"], props => !props.linear ? props.theme.space.xxs : 0, props => props.theme.colors.neutral['000'], props => props.isLoading === 'top' ? "\n top: -41px;\n transform: translate(0, 41px);\n visibility: visible;\n pointer-events: auto;\n " : '', props => props.isLoading === 'bottom' ? "\n bottom: -41px;\n transform: translate(0, -41px);\n visibility: visible;\n pointer-events: auto;\n " : '');
|
|
14
14
|
const MobileStyledChatContainerContent = /*#__PURE__*/styled(StyledChatContainerContent).withConfig({
|
|
15
15
|
componentId: "sc-d732wo-2"
|
|
16
16
|
})(["", ",", "{font-size:16px;}", "{font-size:13px;}", "{background-color:#fff;box-shadow:-0 1px 3px rgba(53,60,70,0.8);}", ",", "{font-size:16px;text-align:left;}", "{font-size:13px;}", "{font-size:16px;}", "{font-size:16px;}"], StyledTitleLeftBubble, StyleBodyWrapper, StyledTitleRightBubble, Wrapper, Title, Message, Time, StyledComposerInput, StyledIndicatorContainer);
|
|
@@ -15,6 +15,7 @@ import React from 'react';
|
|
|
15
15
|
import { describe } from 'react-desc';
|
|
16
16
|
import { useVirtual } from 'react-virtual';
|
|
17
17
|
import DSButton from '@elliemae/ds-button';
|
|
18
|
+
import { debounce } from 'lodash';
|
|
18
19
|
import { ChatBubble } from '../bubble/ChatBubble.js';
|
|
19
20
|
import { BUBBLE_TYPES } from '../bubble/props.js';
|
|
20
21
|
import { MessageDelimiter } from '../message-delimiter/MessageDelimiter.js';
|
|
@@ -57,6 +58,7 @@ const ChatContainerMessagesList = props => {
|
|
|
57
58
|
isLoading,
|
|
58
59
|
bannerRef
|
|
59
60
|
} = props;
|
|
61
|
+
const pendingOnScroll = React.useRef(false);
|
|
60
62
|
const loadMoreRef = React.useRef(null);
|
|
61
63
|
const virtualListRef = React.useRef(); // estimateSize should not be really required given what was stated on
|
|
62
64
|
// https://github.com/tannerlinsley/react-virtual/issues/23
|
|
@@ -77,9 +79,16 @@ const ChatContainerMessagesList = props => {
|
|
|
77
79
|
estimateSize: React.useCallback(() => 80, [])
|
|
78
80
|
}));
|
|
79
81
|
const newMessages = useGetNewMessages(messages);
|
|
80
|
-
const
|
|
82
|
+
const debouncedScroll = React.useMemo(() => debounce(() => {
|
|
81
83
|
var _virtualListRef$curre;
|
|
82
84
|
|
|
85
|
+
if (document.visibilityState === 'hidden') {
|
|
86
|
+
pendingOnScroll.current = true;
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (!virtualListRef.current) return [];
|
|
91
|
+
pendingOnScroll.current = false;
|
|
83
92
|
const {
|
|
84
93
|
bottom,
|
|
85
94
|
top
|
|
@@ -88,15 +97,15 @@ const ChatContainerMessagesList = props => {
|
|
|
88
97
|
const indexList = [];
|
|
89
98
|
list.forEach(item => {
|
|
90
99
|
const {
|
|
91
|
-
top: itemTop
|
|
92
|
-
bottom: itemBottom
|
|
100
|
+
top: itemTop
|
|
93
101
|
} = item.getBoundingClientRect();
|
|
94
102
|
const itemPadding = 10;
|
|
95
|
-
if (itemTop + itemPadding < bottom &&
|
|
103
|
+
if (itemTop + itemPadding < bottom && itemTop + itemPadding > top) indexList.push(item.dataset.index);
|
|
96
104
|
});
|
|
97
105
|
if (onScrollEnds) onScrollEnds(indexList);
|
|
98
106
|
return indexList;
|
|
99
|
-
}, [onScrollEnds]);
|
|
107
|
+
}, 500), [onScrollEnds]);
|
|
108
|
+
const onScrollStop = React.useCallback(debouncedScroll, [debouncedScroll]);
|
|
100
109
|
const onlyFocus = React.useCallback(index => {
|
|
101
110
|
setTimeout(() => {
|
|
102
111
|
virtualListRef.current.querySelector("[data-index=\"".concat(index, "\"]")).focus();
|
|
@@ -108,7 +117,8 @@ const ChatContainerMessagesList = props => {
|
|
|
108
117
|
};
|
|
109
118
|
vScroll(index, b);
|
|
110
119
|
onlyFocus(index);
|
|
111
|
-
|
|
120
|
+
onScrollStop();
|
|
121
|
+
}, [onScrollStop, onlyFocus, vScroll]);
|
|
112
122
|
React.useEffect(() => {
|
|
113
123
|
if (actionRef && actionRef.current) {
|
|
114
124
|
actionRef.current.scrollToIndex = function (a) {
|
|
@@ -116,9 +126,11 @@ const ChatContainerMessagesList = props => {
|
|
|
116
126
|
align: 'center'
|
|
117
127
|
};
|
|
118
128
|
vScroll(a, b);
|
|
129
|
+
onScrollStop();
|
|
119
130
|
};
|
|
120
131
|
|
|
121
132
|
actionRef.current.getMessagesInView = onScrollStop;
|
|
133
|
+
onScrollStop(); // init useMemo due to debounce use.
|
|
122
134
|
|
|
123
135
|
actionRef.current.focusToIndex = function (a) {
|
|
124
136
|
let b = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
|
|
@@ -129,7 +141,7 @@ const ChatContainerMessagesList = props => {
|
|
|
129
141
|
|
|
130
142
|
actionRef.current.focusToIndexWithoutScroll = onlyFocus;
|
|
131
143
|
|
|
132
|
-
actionRef.current.focusToList =
|
|
144
|
+
actionRef.current.focusToList = () => {
|
|
133
145
|
var _virtualListRef$curre2;
|
|
134
146
|
|
|
135
147
|
(_virtualListRef$curre2 = virtualListRef.current) === null || _virtualListRef$curre2 === void 0 ? void 0 : _virtualListRef$curre2.focus();
|
|
@@ -146,7 +158,17 @@ const ChatContainerMessagesList = props => {
|
|
|
146
158
|
switch (key) {
|
|
147
159
|
case 'ArrowUp':
|
|
148
160
|
e.preventDefault();
|
|
149
|
-
|
|
161
|
+
|
|
162
|
+
if (index > 0) {
|
|
163
|
+
var _virtualListRef$curre3;
|
|
164
|
+
|
|
165
|
+
((_virtualListRef$curre3 = virtualListRef.current) === null || _virtualListRef$curre3 === void 0 ? void 0 : _virtualListRef$curre3.querySelector("[data-index=\"".concat(index - 1, "\"]"))).focus();
|
|
166
|
+
} else if (hasMoreItems === 'top') {
|
|
167
|
+
var _loadMoreRef$current;
|
|
168
|
+
|
|
169
|
+
(_loadMoreRef$current = loadMoreRef.current) === null || _loadMoreRef$current === void 0 ? void 0 : _loadMoreRef$current.focus();
|
|
170
|
+
}
|
|
171
|
+
|
|
150
172
|
break;
|
|
151
173
|
|
|
152
174
|
case 'ArrowDown':
|
|
@@ -169,31 +191,31 @@ const ChatContainerMessagesList = props => {
|
|
|
169
191
|
break;
|
|
170
192
|
|
|
171
193
|
case 'Tab':
|
|
172
|
-
e.preventDefault();
|
|
173
|
-
|
|
174
194
|
if (hasMoreItems === 'bottom' && !e.shiftKey) {
|
|
175
|
-
var _loadMoreRef$current;
|
|
176
|
-
|
|
177
|
-
(_loadMoreRef$current = loadMoreRef.current) === null || _loadMoreRef$current === void 0 ? void 0 : _loadMoreRef$current.focus();
|
|
178
|
-
} else if (hasMoreItems === 'top' && e.shiftKey) {
|
|
179
195
|
var _loadMoreRef$current2;
|
|
180
196
|
|
|
197
|
+
e.preventDefault();
|
|
181
198
|
(_loadMoreRef$current2 = loadMoreRef.current) === null || _loadMoreRef$current2 === void 0 ? void 0 : _loadMoreRef$current2.focus();
|
|
199
|
+
} else if (hasMoreItems === 'top' && e.shiftKey) {
|
|
200
|
+
var _loadMoreRef$current3;
|
|
201
|
+
|
|
202
|
+
e.preventDefault();
|
|
203
|
+
(_loadMoreRef$current3 = loadMoreRef.current) === null || _loadMoreRef$current3 === void 0 ? void 0 : _loadMoreRef$current3.focus();
|
|
182
204
|
} else if (!e.shiftKey) {
|
|
183
205
|
if (bannerPosition) {
|
|
206
|
+
e.preventDefault();
|
|
184
207
|
bannerRef.current.focusOnWrapper();
|
|
185
|
-
} else {
|
|
186
|
-
actionRef.current.focusToComposer();
|
|
187
208
|
}
|
|
188
209
|
} else {
|
|
189
|
-
var _virtualListRef$
|
|
210
|
+
var _virtualListRef$curre4;
|
|
190
211
|
|
|
191
|
-
|
|
212
|
+
e.preventDefault();
|
|
213
|
+
(_virtualListRef$curre4 = virtualListRef.current) === null || _virtualListRef$curre4 === void 0 ? void 0 : _virtualListRef$curre4.focus();
|
|
192
214
|
}
|
|
193
215
|
|
|
194
216
|
break;
|
|
195
217
|
}
|
|
196
|
-
}, [
|
|
218
|
+
}, [bannerRef, hasMoreItems, messages.length, bannerPosition, scrollAndFocus]);
|
|
197
219
|
const handleListKey = React.useCallback(e => {
|
|
198
220
|
const {
|
|
199
221
|
key
|
|
@@ -202,12 +224,22 @@ const ChatContainerMessagesList = props => {
|
|
|
202
224
|
switch (key) {
|
|
203
225
|
case 'ArrowUp':
|
|
204
226
|
e.preventDefault();
|
|
205
|
-
|
|
227
|
+
|
|
228
|
+
if (document.activeElement !== loadMoreRef.current) {
|
|
229
|
+
onlyFocus(onScrollStop().shift());
|
|
230
|
+
}
|
|
231
|
+
|
|
206
232
|
break;
|
|
207
233
|
|
|
208
234
|
case 'ArrowDown':
|
|
209
235
|
e.preventDefault();
|
|
210
|
-
|
|
236
|
+
|
|
237
|
+
if (document.activeElement === loadMoreRef.current) {
|
|
238
|
+
virtualListRef.current.querySelector("[data-index=\"0\"]").focus();
|
|
239
|
+
} else {
|
|
240
|
+
onlyFocus(onScrollStop().pop());
|
|
241
|
+
}
|
|
242
|
+
|
|
211
243
|
break;
|
|
212
244
|
|
|
213
245
|
case 'Home':
|
|
@@ -223,11 +255,6 @@ const ChatContainerMessagesList = props => {
|
|
|
223
255
|
case 'Tab':
|
|
224
256
|
if (document.activeElement !== loadMoreRef.current) {
|
|
225
257
|
if (hasMoreItems === 'bottom' && !e.shiftKey) {
|
|
226
|
-
var _loadMoreRef$current3;
|
|
227
|
-
|
|
228
|
-
e.preventDefault();
|
|
229
|
-
(_loadMoreRef$current3 = loadMoreRef.current) === null || _loadMoreRef$current3 === void 0 ? void 0 : _loadMoreRef$current3.focus();
|
|
230
|
-
} else if (hasMoreItems === 'top' && e.shiftKey) {
|
|
231
258
|
var _loadMoreRef$current4;
|
|
232
259
|
|
|
233
260
|
e.preventDefault();
|
|
@@ -240,15 +267,18 @@ const ChatContainerMessagesList = props => {
|
|
|
240
267
|
e.preventDefault();
|
|
241
268
|
actionRef.current.focusToComposer();
|
|
242
269
|
} else {
|
|
243
|
-
var _virtualListRef$
|
|
270
|
+
var _virtualListRef$curre5;
|
|
244
271
|
|
|
245
272
|
e.preventDefault();
|
|
246
|
-
(_virtualListRef$
|
|
273
|
+
(_virtualListRef$curre5 = virtualListRef.current) === null || _virtualListRef$curre5 === void 0 ? void 0 : _virtualListRef$curre5.focus();
|
|
247
274
|
}
|
|
248
275
|
|
|
249
276
|
break;
|
|
250
277
|
}
|
|
251
278
|
}, [actionRef, hasMoreItems, messages.length, onScrollStop, onlyFocus, scrollAndFocus]);
|
|
279
|
+
const checkPendingOnScroll = React.useCallback(() => {
|
|
280
|
+
if (pendingOnScroll.current === true) onScrollStop();
|
|
281
|
+
}, [onScrollStop]);
|
|
252
282
|
React.useEffect(() => {
|
|
253
283
|
let handlerCallback = null;
|
|
254
284
|
const localVirtualRef = virtualListRef.current;
|
|
@@ -262,11 +292,13 @@ const ChatContainerMessagesList = props => {
|
|
|
262
292
|
virtualListRef.current.addEventListener('scroll', onScroll);
|
|
263
293
|
}
|
|
264
294
|
|
|
295
|
+
document.addEventListener('visibilitychange', checkPendingOnScroll);
|
|
265
296
|
return () => {
|
|
266
297
|
if (handlerCallback) clearTimeout(handlerCallback);
|
|
267
298
|
if (onScrollEnds) localVirtualRef.removeEventListener('scroll', onScroll);
|
|
299
|
+
document.removeEventListener('visibilitychange', checkPendingOnScroll);
|
|
268
300
|
};
|
|
269
|
-
}, [onScrollStop, onScrollEnds]);
|
|
301
|
+
}, [onScrollStop, onScrollEnds, checkPendingOnScroll]);
|
|
270
302
|
const render = React.useMemo(() => virtualItems.map(virtualItem => {
|
|
271
303
|
const {
|
|
272
304
|
index,
|
|
@@ -287,7 +319,7 @@ const ChatContainerMessagesList = props => {
|
|
|
287
319
|
"data-index": index,
|
|
288
320
|
style: style,
|
|
289
321
|
ref: measureRef,
|
|
290
|
-
tabIndex:
|
|
322
|
+
tabIndex: -1,
|
|
291
323
|
onKeyDown: handleKey,
|
|
292
324
|
role: "listitem",
|
|
293
325
|
children: /*#__PURE__*/_jsx(StyledWrapperListItem, {
|
|
@@ -303,14 +335,12 @@ const ChatContainerMessagesList = props => {
|
|
|
303
335
|
return /*#__PURE__*/_jsx(StyledWrapper, {}, void 0, /*#__PURE__*/jsx(StyledChatContainerContentList, {
|
|
304
336
|
"data-testid": "chat-thread-container",
|
|
305
337
|
tabIndex: 0,
|
|
306
|
-
"aria-live": "polite",
|
|
307
|
-
"aria-relevant": "additions",
|
|
308
338
|
ref: virtualListRef,
|
|
309
339
|
role: "list",
|
|
310
340
|
onKeyDown: handleListKey,
|
|
311
341
|
children: /*#__PURE__*/_jsx(StyledChatContainerContentListScroll, {
|
|
312
342
|
height: "".concat(totalSize, "px")
|
|
313
|
-
}, void 0,
|
|
343
|
+
}, void 0, !isLoading && hasMoreItems ? /*#__PURE__*/_jsx(StyledLoadMoreContainer, {
|
|
314
344
|
hasMoreItems: hasMoreItems
|
|
315
345
|
}, void 0, /*#__PURE__*/_jsx(DSButton, {
|
|
316
346
|
labelText: "Load More",
|
|
@@ -318,7 +348,7 @@ const ChatContainerMessagesList = props => {
|
|
|
318
348
|
onClick: getMoreMessages,
|
|
319
349
|
"data-testid": "ds-chat-load-more-btn",
|
|
320
350
|
innerRef: loadMoreRef
|
|
321
|
-
})) : null)
|
|
351
|
+
})) : null, render)
|
|
322
352
|
}), /*#__PURE__*/_jsx(StyledNewMessagesHiddenContainer, {
|
|
323
353
|
"aria-live": "polite",
|
|
324
354
|
"aria-hidden": "false"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elliemae/ds-chat",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.6",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "ICE MT - Dimsum - Chat",
|
|
6
6
|
"module": "./esm/index.js",
|
|
@@ -309,15 +309,15 @@
|
|
|
309
309
|
"generateSubmodules": true
|
|
310
310
|
},
|
|
311
311
|
"dependencies": {
|
|
312
|
-
"@elliemae/ds-banner": "2.0.
|
|
313
|
-
"@elliemae/ds-button": "2.0.
|
|
314
|
-
"@elliemae/ds-grid": "2.0.
|
|
315
|
-
"@elliemae/ds-icons": "2.0.
|
|
316
|
-
"@elliemae/ds-indeterminate-progress-indicator": "2.0.
|
|
317
|
-
"@elliemae/ds-props-helpers": "2.0.
|
|
318
|
-
"@elliemae/ds-system": "2.0.
|
|
319
|
-
"@elliemae/ds-tooltip": "2.0.
|
|
320
|
-
"@elliemae/ds-truncated-tooltip-text": "2.0.
|
|
312
|
+
"@elliemae/ds-banner": "2.0.6",
|
|
313
|
+
"@elliemae/ds-button": "2.0.6",
|
|
314
|
+
"@elliemae/ds-grid": "2.0.6",
|
|
315
|
+
"@elliemae/ds-icons": "2.0.6",
|
|
316
|
+
"@elliemae/ds-indeterminate-progress-indicator": "2.0.6",
|
|
317
|
+
"@elliemae/ds-props-helpers": "2.0.6",
|
|
318
|
+
"@elliemae/ds-system": "2.0.6",
|
|
319
|
+
"@elliemae/ds-tooltip": "2.0.6",
|
|
320
|
+
"@elliemae/ds-truncated-tooltip-text": "2.0.6",
|
|
321
321
|
"prop-types": "~15.7.2",
|
|
322
322
|
"react-desc": "~4.1.2",
|
|
323
323
|
"react-virtual": "~2.3.2"
|
|
@@ -10,6 +10,9 @@ declare const ChatComposer: {
|
|
|
10
10
|
inputValue: {
|
|
11
11
|
deprecated: import("react-desc").PropTypesDescValidator;
|
|
12
12
|
};
|
|
13
|
+
ariaMaxLengthMessage: {
|
|
14
|
+
deprecated: import("react-desc").PropTypesDescValidator;
|
|
15
|
+
};
|
|
13
16
|
maxLengthMessage: {
|
|
14
17
|
deprecated: import("react-desc").PropTypesDescValidator;
|
|
15
18
|
};
|
package/types/tests/utils.d.ts
CHANGED
|
@@ -1,27 +1 @@
|
|
|
1
|
-
export declare const
|
|
2
|
-
dsId: string;
|
|
3
|
-
body: string;
|
|
4
|
-
type: string;
|
|
5
|
-
title: string;
|
|
6
|
-
time: string;
|
|
7
|
-
extraProps?: undefined;
|
|
8
|
-
helpMessage?: undefined;
|
|
9
|
-
} | {
|
|
10
|
-
dsId: string;
|
|
11
|
-
type: string;
|
|
12
|
-
title: string;
|
|
13
|
-
extraProps: {
|
|
14
|
-
'data-testid': string;
|
|
15
|
-
};
|
|
16
|
-
body?: undefined;
|
|
17
|
-
time?: undefined;
|
|
18
|
-
helpMessage?: undefined;
|
|
19
|
-
} | {
|
|
20
|
-
dsId: string;
|
|
21
|
-
body: string;
|
|
22
|
-
type: string;
|
|
23
|
-
title: string;
|
|
24
|
-
time: string;
|
|
25
|
-
helpMessage: string | null;
|
|
26
|
-
extraProps?: undefined;
|
|
27
|
-
})[];
|
|
1
|
+
export declare const loadMsgs: (q: number) => unknown;
|