@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.
@@ -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 // maxLength={inputMaxLength}
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, maxLengthMessage || "You have exceeded the character limit by ".concat(inputLength - inputMaxLength)), maxLengthMessage || "Please limit to less than ".concat(inputMaxLength, " characters. ")]
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, _StyledIndicatorConta;
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
- setTimeout(() => {
77
- actionRef.current.scrollToIndex(messagesListProps.messages.length - 1, {
78
- align: 'start'
79
- });
80
- }, 50);
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
- e.preventDefault();
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
- if (['top', 'bottom'].includes(bannerPosition)) {
92
- bannerRef.current.focusOnWrapper();
93
- } else {
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 ? _StyledIndicatorConta || (_StyledIndicatorConta = /*#__PURE__*/_jsx__default["default"](styled.StyledIndicatorContainer, {
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: "Loading",
159
+ title: sendingText,
156
160
  lineOnly: true
157
- }))) : null), /*#__PURE__*/jsxRuntime.jsx(ChatContainerComposer.ChatComposer, _objectSpread(_objectSpread({}, composerProps), {}, {
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\n " : '', props => props.isLoading === 'bottom' ? "\n bottom: -41px;\n transform: translate(0, -41px);\n visibility: visible;\n pointer-events: auto;\n " : '');
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 onScrollStop = React__default["default"].useCallback(() => {
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 && itemBottom - itemPadding > top) indexList.push(item.dataset.index);
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
- }, [onlyFocus, vScroll]);
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 = a => {
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
- if (index > 0) virtualListRef.current.querySelector("[data-index=\"".concat(index - 1, "\"]")).focus();
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$curre3;
221
+ var _virtualListRef$curre4;
201
222
 
202
- (_virtualListRef$curre3 = virtualListRef.current) === null || _virtualListRef$curre3 === void 0 ? void 0 : _virtualListRef$curre3.focus();
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
- }, [actionRef, hasMoreItems, messages.length, bannerPosition, scrollAndFocus]);
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
- onlyFocus(onScrollStop().shift());
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
- onlyFocus(onScrollStop().pop());
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$curre4;
281
+ var _virtualListRef$curre5;
255
282
 
256
283
  e.preventDefault();
257
- (_virtualListRef$curre4 = virtualListRef.current) === null || _virtualListRef$curre4 === void 0 ? void 0 : _virtualListRef$curre4.focus();
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: 0,
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, render, !isLoading && hasMoreItems ? /*#__PURE__*/_jsx__default["default"](styled.StyledLoadMoreContainer, {
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"
@@ -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 // maxLength={inputMaxLength}
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, maxLengthMessage || "You have exceeded the character limit by ".concat(inputLength - inputMaxLength)), maxLengthMessage || "Please limit to less than ".concat(inputMaxLength, " characters. ")]
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, _StyledIndicatorConta;
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
- setTimeout(() => {
65
- actionRef.current.scrollToIndex(messagesListProps.messages.length - 1, {
66
- align: 'start'
67
- });
68
- }, 50);
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
- e.preventDefault();
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
- if (['top', 'bottom'].includes(bannerPosition)) {
80
- bannerRef.current.focusOnWrapper();
81
- } else {
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 ? _StyledIndicatorConta || (_StyledIndicatorConta = /*#__PURE__*/_jsx(StyledIndicatorContainer, {
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: "Loading",
147
+ title: sendingText,
144
148
  lineOnly: true
145
- }))) : null), /*#__PURE__*/jsx(ChatComposer, _objectSpread(_objectSpread({}, composerProps), {}, {
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\n " : '', props => props.isLoading === 'bottom' ? "\n bottom: -41px;\n transform: translate(0, -41px);\n visibility: visible;\n pointer-events: auto;\n " : '');
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 onScrollStop = React.useCallback(() => {
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 && itemBottom - itemPadding > top) indexList.push(item.dataset.index);
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
- }, [onlyFocus, vScroll]);
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 = a => {
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
- if (index > 0) virtualListRef.current.querySelector("[data-index=\"".concat(index - 1, "\"]")).focus();
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$curre3;
210
+ var _virtualListRef$curre4;
190
211
 
191
- (_virtualListRef$curre3 = virtualListRef.current) === null || _virtualListRef$curre3 === void 0 ? void 0 : _virtualListRef$curre3.focus();
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
- }, [actionRef, hasMoreItems, messages.length, bannerPosition, scrollAndFocus]);
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
- onlyFocus(onScrollStop().shift());
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
- onlyFocus(onScrollStop().pop());
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$curre4;
270
+ var _virtualListRef$curre5;
244
271
 
245
272
  e.preventDefault();
246
- (_virtualListRef$curre4 = virtualListRef.current) === null || _virtualListRef$curre4 === void 0 ? void 0 : _virtualListRef$curre4.focus();
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: 0,
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, render, !isLoading && hasMoreItems ? /*#__PURE__*/_jsx(StyledLoadMoreContainer, {
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.5",
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.5",
313
- "@elliemae/ds-button": "2.0.5",
314
- "@elliemae/ds-grid": "2.0.5",
315
- "@elliemae/ds-icons": "2.0.5",
316
- "@elliemae/ds-indeterminate-progress-indicator": "2.0.5",
317
- "@elliemae/ds-props-helpers": "2.0.5",
318
- "@elliemae/ds-system": "2.0.5",
319
- "@elliemae/ds-tooltip": "2.0.5",
320
- "@elliemae/ds-truncated-tooltip-text": "2.0.5",
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"
@@ -18,4 +18,5 @@ export declare const ChatDataTestIds: {
18
18
  BUBBLE: string;
19
19
  BUBBLE_TITLE: string;
20
20
  BUBBLE_BODY: string;
21
+ BANNER: string;
21
22
  };
@@ -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
  };
@@ -1,27 +1 @@
1
- export declare const preLoadMessages2: ({
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;