@elliemae/ds-chat 2.0.0-next.9 → 2.0.0-rc.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/cjs/ChatDataTestids.js +27 -0
  2. package/cjs/parts/bubble/ChatBubble.js +9 -6
  3. package/cjs/parts/bubble/ChatBubbleArrow.js +64 -61
  4. package/cjs/parts/bubble/props.js +1 -1
  5. package/cjs/parts/bubble/styled.js +1 -1
  6. package/cjs/parts/chat-composer/ChatContainerComposer.js +14 -7
  7. package/cjs/parts/chat-composer/props.js +4 -0
  8. package/cjs/parts/chat-composer/styled.js +3 -3
  9. package/cjs/parts/chat-container/ChatContainer.js +67 -20
  10. package/cjs/parts/chat-container/config/useValidateProps.js +2 -2
  11. package/cjs/parts/chat-container/props.js +7 -3
  12. package/cjs/parts/chat-container/styled.js +1 -1
  13. package/cjs/parts/chat-container-header/ChatContainerHeader.js +3 -3
  14. package/cjs/parts/chat-container-header/helpers.js +31 -0
  15. package/cjs/parts/chat-container-header/styled.js +6 -6
  16. package/cjs/parts/chat-container-messages-list/ChatContainerMessagesList.js +151 -35
  17. package/cjs/parts/chat-container-messages-list/config/useValidateProps.js +2 -2
  18. package/cjs/parts/chat-container-messages-list/props.js +4 -2
  19. package/cjs/parts/chat-container-messages-list/styled.js +2 -2
  20. package/cjs/parts/chat-empty-state/ChatEmptyState.js +2 -3
  21. package/cjs/parts/chat-sidebar/SideBarChat.js +57 -29
  22. package/cjs/parts/chat-sidebar/props.js +11 -1
  23. package/cjs/parts/chat-sidebar/styled.js +2 -2
  24. package/cjs/parts/floating-button/FloatingButton.js +18 -10
  25. package/cjs/parts/floating-button/props.js +6 -2
  26. package/cjs/parts/sidebar-button/SidebarButton.js +5 -0
  27. package/cjs/parts/sidebar-button/props.js +2 -3
  28. package/cjs/parts/system-message/style.js +1 -1
  29. package/cjs/parts/tile-button/TileButton.js +30 -27
  30. package/cjs/parts/tile-button/props.js +12 -8
  31. package/cjs/parts/tile-button/styled.js +11 -4
  32. package/esm/ChatDataTestids.js +23 -0
  33. package/esm/parts/bubble/ChatBubble.js +8 -5
  34. package/esm/parts/bubble/ChatBubbleArrow.js +64 -61
  35. package/esm/parts/bubble/props.js +1 -1
  36. package/esm/parts/bubble/styled.js +1 -1
  37. package/esm/parts/chat-composer/ChatContainerComposer.js +13 -5
  38. package/esm/parts/chat-composer/props.js +4 -0
  39. package/esm/parts/chat-composer/styled.js +1 -1
  40. package/esm/parts/chat-container/ChatContainer.js +65 -18
  41. package/esm/parts/chat-container/config/useValidateProps.js +2 -2
  42. package/esm/parts/chat-container/props.js +7 -3
  43. package/esm/parts/chat-container/styled.js +1 -1
  44. package/esm/parts/chat-container-header/ChatContainerHeader.js +3 -2
  45. package/esm/parts/chat-container-header/helpers.js +23 -0
  46. package/esm/parts/chat-container-header/styled.js +6 -6
  47. package/esm/parts/chat-container-messages-list/ChatContainerMessagesList.js +149 -33
  48. package/esm/parts/chat-container-messages-list/config/useValidateProps.js +2 -2
  49. package/esm/parts/chat-container-messages-list/props.js +4 -2
  50. package/esm/parts/chat-container-messages-list/styled.js +2 -2
  51. package/esm/parts/chat-empty-state/ChatEmptyState.js +1 -1
  52. package/esm/parts/chat-sidebar/SideBarChat.js +57 -28
  53. package/esm/parts/chat-sidebar/props.js +11 -1
  54. package/esm/parts/chat-sidebar/styled.js +2 -2
  55. package/esm/parts/floating-button/FloatingButton.js +18 -9
  56. package/esm/parts/floating-button/props.js +6 -2
  57. package/esm/parts/sidebar-button/SidebarButton.js +5 -0
  58. package/esm/parts/sidebar-button/props.js +1 -1
  59. package/esm/parts/system-message/style.js +1 -1
  60. package/esm/parts/tile-button/TileButton.js +31 -26
  61. package/esm/parts/tile-button/props.js +11 -3
  62. package/esm/parts/tile-button/styled.js +11 -6
  63. package/package.json +28 -6
  64. package/types/ChatDataTestids.d.ts +21 -0
  65. package/types/parts/bubble/ChatBubble.d.ts +18 -5
  66. package/types/parts/bubble/props.d.ts +13 -4
  67. package/types/parts/bubble/styled.d.ts +9 -9
  68. package/types/parts/bubble/tests/ChatBubble.test.d.ts +1 -0
  69. package/types/parts/chat-card/ChatCard.d.ts +24 -4
  70. package/types/parts/chat-card/props.d.ts +19 -3
  71. package/types/parts/chat-card/styled.d.ts +6 -6
  72. package/types/parts/chat-composer/ChatContainerComposer.d.ts +48 -13
  73. package/types/parts/chat-composer/styled.d.ts +117 -24
  74. package/types/parts/chat-composer/tests/ChatContainerComposer.test.d.ts +1 -0
  75. package/types/parts/chat-container/ChatContainer.d.ts +54 -13
  76. package/types/parts/chat-container/config/useValidateProps.d.ts +1 -1
  77. package/types/parts/chat-container/styled.d.ts +4 -4
  78. package/types/parts/chat-container/tests/ChatContainer.test.d.ts +1 -0
  79. package/types/parts/chat-container-header/helpers.d.ts +6 -0
  80. package/types/parts/chat-container-header/styled.d.ts +3 -3
  81. package/types/parts/chat-container-messages-list/ChatContainerMessagesList.d.ts +39 -9
  82. package/types/parts/chat-container-messages-list/config/useValidateProps.d.ts +1 -1
  83. package/types/parts/chat-container-messages-list/styled.d.ts +7 -7
  84. package/types/parts/chat-empty-state/ChatEmptyState.d.ts +12 -3
  85. package/types/parts/chat-empty-state/props.d.ts +7 -2
  86. package/types/parts/chat-empty-state/styled.d.ts +2 -2
  87. package/types/parts/chat-sidebar/SideBarChat.d.ts +39 -9
  88. package/types/parts/chat-sidebar/props.d.ts +39 -9
  89. package/types/parts/chat-sidebar/styled.d.ts +2 -2
  90. package/types/parts/floating-button/FloatingButton.d.ts +11 -2
  91. package/types/parts/floating-button/props.d.ts +13 -1
  92. package/types/parts/floating-button/styled.d.ts +1 -1
  93. package/types/parts/floating-container/styled.d.ts +1 -2
  94. package/types/parts/grid-influencer/styled.d.ts +2 -2
  95. package/types/parts/message-delimiter/MessageDelimiter.d.ts +21 -4
  96. package/types/parts/message-delimiter/props.d.ts +16 -3
  97. package/types/parts/message-delimiter/styled.d.ts +3 -3
  98. package/types/parts/sidebar-button/SidebarButton.d.ts +16 -5
  99. package/types/parts/sidebar-button/props.d.ts +16 -5
  100. package/types/parts/sidebar-button/styled.d.ts +3 -3
  101. package/types/parts/system-message/SystemMessage.d.ts +12 -2
  102. package/types/parts/system-message/props.d.ts +7 -1
  103. package/types/parts/system-message/style.d.ts +5 -5
  104. package/types/parts/tile-button/TileButton.d.ts +66 -17
  105. package/types/parts/tile-button/props.d.ts +64 -16
  106. package/types/parts/tile-button/styled.d.ts +9 -8
  107. package/types/prop-types.d.ts +28 -9
  108. package/types/tests/utils.d.ts +27 -0
  109. package/types/parts/chat-composer/props.d.ts +0 -35
  110. package/types/parts/chat-container/props.d.ts +0 -39
  111. package/types/parts/chat-container-messages-list/props.d.ts +0 -23
@@ -1,9 +1,14 @@
1
+ import 'core-js/modules/esnext.async-iterator.filter.js';
2
+ import 'core-js/modules/esnext.iterator.constructor.js';
3
+ import 'core-js/modules/esnext.iterator.filter.js';
4
+ import 'core-js/modules/esnext.async-iterator.for-each.js';
5
+ import 'core-js/modules/esnext.iterator.for-each.js';
1
6
  import _jsx from '@babel/runtime/helpers/esm/jsx';
2
7
  import _defineProperty from '@babel/runtime/helpers/esm/defineProperty';
3
8
  import React from 'react';
4
9
  import { noop } from 'lodash';
5
10
  import { describe } from 'react-desc';
6
- import DSIndeterminateProgressIndicator from '@elliemae/ds-basic/IndeterminateProgressIndicator';
11
+ import DSIndeterminateProgressIndicator from '@elliemae/ds-indeterminate-progress-indicator';
7
12
  import DSBanner, { BANNER_TYPES } from '@elliemae/ds-banner';
8
13
  import { ChatComposer } from '../chat-composer/ChatContainerComposer.js';
9
14
  import { ChatContainerMessagesList } from '../chat-container-messages-list/ChatContainerMessagesList.js';
@@ -21,7 +26,7 @@ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { va
21
26
  const ChatContainer = props => {
22
27
  var _containerRef$current;
23
28
 
24
- useValidateProps(props, ChatContainerWithSchema.toTypescript());
29
+ useValidateProps(props, containerProps);
25
30
  const {
26
31
  sendingText,
27
32
  sendingMessages,
@@ -30,15 +35,20 @@ const ChatContainer = props => {
30
35
  messagesListProps,
31
36
  composerProps,
32
37
  composerProps: {
33
- onResize: onComposerResize
38
+ onResize: onComposerResize,
39
+ onKeyDown: onComposerKeyDown
34
40
  },
35
41
  actionRef,
36
- autoScroll,
37
42
  device,
38
43
  isLoading,
39
44
  hasMoreItems,
40
- getMoreMessages
45
+ getMoreMessages,
46
+ ariaLabel
41
47
  } = props;
48
+ const bannerRef = React.useRef({
49
+ focusOnWrapper: noop,
50
+ focusOnLink: noop
51
+ });
42
52
  const containerRef = React.useRef();
43
53
  const listRef = React.useRef({
44
54
  scrollToIndex: noop
@@ -46,21 +56,51 @@ const ChatContainer = props => {
46
56
  const onResize = React.useCallback(height => {
47
57
  if (onComposerResize) onComposerResize(height);
48
58
 
49
- if (autoScroll) {
50
- listRef.current.scrollToIndex(messagesListProps.messages.length - 1, {
51
- align: 'end'
52
- });
59
+ if (height > 30) {
60
+ /*
61
+ setTimeout is used to equal the animation duration from the
62
+ composer text area when increase its size
63
+ */
64
+ setTimeout(() => {
65
+ actionRef.current.scrollToIndex(messagesListProps.messages.length - 1, {
66
+ align: 'start'
67
+ });
68
+ }, 50);
53
69
  }
54
- }, [onComposerResize, messagesListProps.messages, autoScroll]);
70
+ }, [onComposerResize, messagesListProps.messages, actionRef]);
55
71
  const Container = device === 'desktop' ? StyledChatContainerContent : MobileStyledChatContainerContent;
72
+ const handleTabs = React.useCallback(e => {
73
+ if (onComposerKeyDown) onComposerKeyDown(e);else {
74
+ const ref = actionRef || listRef;
75
+
76
+ if (e.shiftKey && e.key === 'Tab') {
77
+ e.preventDefault();
78
+
79
+ if (['top', 'bottom'].includes(bannerPosition)) {
80
+ bannerRef.current.focusOnWrapper();
81
+ } else {
82
+ const last = ref.current.getMessagesInView().pop();
83
+ ref.current.focusToIndexWithoutScroll(last);
84
+ }
85
+ }
86
+ }
87
+ }, [actionRef, onComposerKeyDown, bannerPosition]); // Map actions
88
+
89
+ React.useEffect(() => {
90
+ if (actionRef && actionRef.current) {
91
+ actionRef.current.focusOnBanner = bannerRef.current.focusOnWrapper;
92
+ actionRef.current.focusOnBannerLink = bannerRef.current.focusOnLink;
93
+ }
94
+ }, [actionRef]);
56
95
  return /*#__PURE__*/jsxs(Container, {
57
96
  rows: ['minmax(0, 1fr)', 'auto'],
58
97
  ref: containerRef,
59
98
  role: "region",
60
- "aria-label": "chat window",
99
+ "aria-label": ariaLabel,
61
100
  children: [/*#__PURE__*/_jsx("div", {
62
101
  style: {
63
- position: 'relative'
102
+ position: 'relative',
103
+ overflow: 'hidden'
64
104
  }
65
105
  }, void 0, /*#__PURE__*/jsx(ChatContainerMessagesList, _objectSpread(_objectSpread({}, messagesListProps), {}, {
66
106
  actionRef: actionRef || listRef,
@@ -68,13 +108,17 @@ const ChatContainer = props => {
68
108
  hasMoreItems: hasMoreItems,
69
109
  getMoreMessages: getMoreMessages,
70
110
  sendingMessages: sendingMessages,
71
- sendingText: sendingText
111
+ sendingText: sendingText,
112
+ bannerPosition: bannerPosition,
113
+ bannerRef: bannerRef
72
114
  })), ['top', 'bottom'].includes(isLoading) ? /*#__PURE__*/_jsx(StyledIndicatorContainer, {
73
- isLoading: isLoading
115
+ isLoading: isLoading,
116
+ "data-testid": "ds-chat-loading-indicator"
74
117
  }, void 0, _DSIndeterminateProgr || (_DSIndeterminateProgr = /*#__PURE__*/_jsx(DSIndeterminateProgressIndicator, {
75
118
  processing: true,
76
119
  title: "Loading"
77
- }))) : null, ['top', 'bottom'].includes(bannerPosition) ? /*#__PURE__*/_jsx(StyledIndicatorContainer, {
120
+ }))) : null, /*#__PURE__*/_jsx(StyledIndicatorContainer, {
121
+ linear: true,
78
122
  isLoading: bannerPosition
79
123
  }, void 0, /*#__PURE__*/_jsx(DSBanner, {
80
124
  showCloseButton: true,
@@ -88,15 +132,18 @@ const ChatContainer = props => {
88
132
  },
89
133
  onClose: bannerProps.onClose,
90
134
  isOpen: true,
91
- type: BANNER_TYPES.INFO
92
- })) : null, sendingMessages && sendingMessages.length > 0 ? _StyledIndicatorConta || (_StyledIndicatorConta = /*#__PURE__*/_jsx(StyledIndicatorContainer, {
135
+ type: bannerProps.type || BANNER_TYPES.INFO,
136
+ actionRef: bannerRef
137
+ })), sendingMessages && sendingMessages.length > 0 ? _StyledIndicatorConta || (_StyledIndicatorConta = /*#__PURE__*/_jsx(StyledIndicatorContainer, {
93
138
  isLoading: "bottom",
94
- linear: true
139
+ linear: true,
140
+ "data-testid": "ds-chat-loading-indicator"
95
141
  }, void 0, /*#__PURE__*/_jsx(DSIndeterminateProgressIndicator, {
96
142
  processing: true,
97
143
  title: "Loading",
98
144
  lineOnly: true
99
145
  }))) : null), /*#__PURE__*/jsx(ChatComposer, _objectSpread(_objectSpread({}, composerProps), {}, {
146
+ onKeyDown: handleTabs,
100
147
  onResize: onResize,
101
148
  maxHeight: (((_containerRef$current = containerRef.current) === null || _containerRef$current === void 0 ? void 0 : _containerRef$current.clientHeight) || 0) / 2,
102
149
  actionRef: actionRef || listRef
@@ -1,7 +1,7 @@
1
1
  import { useValidateTypescriptPropTypes } from '@elliemae/ds-props-helpers';
2
2
 
3
- const useValidateProps = (props, schema) => {
4
- useValidateTypescriptPropTypes(props, schema);
3
+ const useValidateProps = (props, propTypes) => {
4
+ useValidateTypescriptPropTypes(props, propTypes);
5
5
  };
6
6
 
7
7
  export { useValidateProps };
@@ -1,5 +1,6 @@
1
1
  import { PropTypes } from 'react-desc';
2
2
  import { noop } from 'lodash';
3
+ import { BANNER_TYPES } from '@elliemae/ds-banner';
3
4
  import { defaultProps as defaultProps$1 } from '../chat-container-messages-list/props.js';
4
5
  import { defaultComposerPros } from '../chat-composer/props.js';
5
6
 
@@ -14,13 +15,15 @@ const defaultProps = {
14
15
  getMoreMessages: noop,
15
16
  bannerPosition: undefined,
16
17
  bannerProps: {
18
+ type: BANNER_TYPES.INFO,
17
19
  label: 'Unread Messages',
18
20
  viewLabel: 'View',
19
21
  onView: noop,
20
22
  onClose: noop
21
23
  },
22
24
  sendingMessages: undefined,
23
- sendingText: 'Sending'
25
+ sendingText: 'Sending',
26
+ ariaLabel: 'Chat window'
24
27
  };
25
28
  const containerProps = {
26
29
  messagesListProps: PropTypes.object.description('ChatMessageList props "messages" / "onScrollEnds"').defaultValue(defaultProps.messagesListProps),
@@ -31,10 +34,11 @@ const containerProps = {
31
34
  hasMoreItems: PropTypes.oneOf(['top', 'bottom', undefined]).description('Show Load More button at the `top` or `bottom` of the container'),
32
35
  getMoreMessages: PropTypes.func.description('Called when the user clicks on load more').defaultValue(defaultProps.getMoreMessages),
33
36
  bannerPosition: PropTypes.oneOf(['top', 'bottom', undefined]).description('Show Loading messages indicator at the `top` or `bottom` of the container'),
34
- bannerProps: PropTypes.object.description('Banner configuration').defaultValue(defaultProps.bannerProps),
37
+ bannerProps: PropTypes.object.description("Banner configuration:\n label: string;\n viewLabel: string;\n type: BANNER_TYPES;\n onView: () => void;\n onClose: () => void;\n ").defaultValue(defaultProps.bannerProps),
35
38
  sendingMessages: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.string), PropTypes.arrayOf(PropTypes.number)]).description('Array of ids of sending messages').defaultValue(defaultProps.sendingMessages),
36
39
  sendingText: PropTypes.string.description('Label description for messages in sending status').defaultValue(defaultProps.sendingText),
37
- actionRef: PropTypes.object.description("Reference: \n { \n scrollToIndex: (n: number) => void,\n getMessagesInView: () => number[],\n focusToIndex: (n: number) => void,\n focusToList: () => void,\n focusToComposer: () => void,\n focusToSend: () => void,\n }\n \n ").defaultValue(defaultProps.actionRef)
40
+ actionRef: PropTypes.object.description("Reference: \n { \n scrollToIndex: (n: number) => void,\n getMessagesInView: () => number[],\n focusToIndex: (n: number) => void,\n focusToList: () => void,\n focusToComposer: () => void,\n focusToSend: () => void,\n }\n \n ").defaultValue(defaultProps.actionRef),
41
+ ariaLabel: PropTypes.string.description('Aria Label description for chat container').defaultValue(defaultProps.ariaLabel)
38
42
  };
39
43
 
40
44
  export { containerProps, defaultProps };
@@ -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
- })(["position:absolute;width:calc(100% + ", ");padding:", ";left:-", ";right:-", ";background:", ";", " ", ""], props => props.theme.space.xxs, props => !props.linear ? props.theme.space.xxs : 0, props => props.theme.space.xxs, props => props.theme.space.xxs, props => props.theme.colors.neutral['000'], props => props.isLoading === 'top' ? 'top: 0;' : '', props => props.isLoading === 'bottom' ? 'bottom: 0;' : '');
13
+ })(["transition:0.4s ease-in-out;position:absolute;visibility:hidden;pointer-events:none;width:100%;padding:0 ", ";left:0;right:0;background:", ";", " ", ""], 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;}"], StyledTitleLeftBubble, StyleBodyWrapper, StyledTitleRightBubble, Wrapper, Title, Message, Time, StyledComposerInput);
@@ -1,7 +1,8 @@
1
1
  import _jsx from '@babel/runtime/helpers/esm/jsx';
2
- import React from 'react';
2
+ import 'react';
3
3
  import { SimpleTruncatedTooltipText } from '@elliemae/ds-truncated-tooltip-text';
4
4
  import { StyledChatContainerContentHeader, StyledChatContainerContentHeaderLabel, StyledChatContainerContentHeaderActions } from './styled.js';
5
+ import { validHeaderChilds } from './helpers.js';
5
6
 
6
7
  const ChatContainerHeader = props => {
7
8
  const {
@@ -21,7 +22,7 @@ const ChatContainerHeader = props => {
21
22
  }, void 0, /*#__PURE__*/_jsx(SimpleTruncatedTooltipText, {
22
23
  value: title
23
24
  })), /*#__PURE__*/_jsx(StyledChatContainerContentHeaderActions, {
24
- cols: React.Children.toArray(children).filter(e => e).map(() => '32px'),
25
+ cols: validHeaderChilds(children),
25
26
  justifyContent: "flex-end",
26
27
  "data-testid": "chat-container-header-children"
27
28
  }, void 0, children));
@@ -0,0 +1,23 @@
1
+ import 'core-js/modules/esnext.async-iterator.map.js';
2
+ import 'core-js/modules/esnext.iterator.map.js';
3
+ import 'core-js/modules/esnext.async-iterator.filter.js';
4
+ import 'core-js/modules/esnext.iterator.constructor.js';
5
+ import 'core-js/modules/esnext.iterator.filter.js';
6
+ import 'core-js/modules/esnext.async-iterator.constructor.js';
7
+ import 'core-js/modules/esnext.async-iterator.to-array.js';
8
+ import 'core-js/modules/esnext.iterator.to-array.js';
9
+ import React from 'react';
10
+
11
+ const validHeaderChilds = children => React.Children.toArray(children).filter(e => {
12
+ if (Array.isArray(e.props.children)) {
13
+ return false;
14
+ }
15
+
16
+ if (e.props.children === undefined && e.$$typeof.toString() === 'Symbol(react.element)') {
17
+ return true;
18
+ }
19
+
20
+ return !!e.props.children;
21
+ }).map(() => '32px');
22
+
23
+ export { validHeaderChilds };
@@ -1,14 +1,14 @@
1
1
  import styled from 'styled-components';
2
2
  import { Grid } from '@elliemae/ds-grid';
3
3
 
4
- const StyledChatContainerContentHeader = /*#__PURE__*/styled(Grid).withConfig({
4
+ const StyledChatContainerContentHeaderActions = /*#__PURE__*/styled(Grid).withConfig({
5
5
  componentId: "sc-jmynvr-0"
6
- })(["width:100%;min-height:48px;border-bottom:1px solid ", ";background:", ";"], props => props.theme.colors.neutral['200'], props => props.theme.colors.neutral['050']);
7
- const StyledChatContainerContentHeaderLabel = /*#__PURE__*/styled(Grid).withConfig({
6
+ })(["max-width:80px;"]);
7
+ const StyledChatContainerContentHeader = /*#__PURE__*/styled(Grid).withConfig({
8
8
  componentId: "sc-jmynvr-1"
9
- })(["justify-content:center;align-items:center;font-weight:bold;font-size:18px;color:", ";"], props => props.theme.colors.neutral['700']);
10
- const StyledChatContainerContentHeaderActions = /*#__PURE__*/styled(Grid).withConfig({
9
+ })(["width:100%;min-height:48px;border-bottom:1px solid ", ";background:", ";", "{& button{background:transparent;}}"], props => props.theme.colors.neutral['200'], props => props.theme.colors.neutral['050'], StyledChatContainerContentHeaderActions);
10
+ const StyledChatContainerContentHeaderLabel = /*#__PURE__*/styled(Grid).withConfig({
11
11
  componentId: "sc-jmynvr-2"
12
- })(["max-width:80px;"]);
12
+ })(["justify-content:center;align-items:center;font-weight:bold;font-size:18px;color:", ";"], props => props.theme.colors.neutral['700']);
13
13
 
14
14
  export { StyledChatContainerContentHeader, StyledChatContainerContentHeaderActions, StyledChatContainerContentHeaderLabel };
@@ -1,9 +1,20 @@
1
+ import 'core-js/modules/esnext.async-iterator.filter.js';
2
+ import 'core-js/modules/esnext.iterator.filter.js';
1
3
  import _jsx from '@babel/runtime/helpers/esm/jsx';
2
4
  import _defineProperty from '@babel/runtime/helpers/esm/defineProperty';
5
+ import 'core-js/modules/esnext.async-iterator.some.js';
6
+ import 'core-js/modules/esnext.iterator.constructor.js';
7
+ import 'core-js/modules/esnext.iterator.some.js';
8
+ import 'core-js/modules/esnext.async-iterator.for-each.js';
9
+ import 'core-js/modules/esnext.iterator.for-each.js';
10
+ import 'core-js/modules/esnext.async-iterator.map.js';
11
+ import 'core-js/modules/esnext.iterator.map.js';
12
+ import 'core-js/modules/esnext.async-iterator.find.js';
13
+ import 'core-js/modules/esnext.iterator.find.js';
3
14
  import React from 'react';
4
15
  import { describe } from 'react-desc';
5
16
  import { useVirtual } from 'react-virtual';
6
- import DSButton from '@elliemae/ds-basic/Button';
17
+ import DSButton from '@elliemae/ds-button';
7
18
  import { ChatBubble } from '../bubble/ChatBubble.js';
8
19
  import { BUBBLE_TYPES } from '../bubble/props.js';
9
20
  import { MessageDelimiter } from '../message-delimiter/MessageDelimiter.js';
@@ -17,27 +28,35 @@ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (O
17
28
 
18
29
  function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
19
30
 
20
- const getPadding = hasMoreItems => {
31
+ const getPadding = _ref => {
32
+ let {
33
+ hasMoreItems,
34
+ bannerPosition,
35
+ isLoading
36
+ } = _ref;
21
37
  const padding = {
22
38
  paddingStart: 0,
23
39
  paddingEnd: -5
24
40
  };
25
- if (hasMoreItems === 'top') padding.paddingStart = 42;else if (hasMoreItems === 'bottom') padding.paddingEnd = 42;
41
+ if ([hasMoreItems, bannerPosition, isLoading].some(item => item === 'top')) padding.paddingStart = 42;else if ([hasMoreItems, bannerPosition, isLoading].some(item => item === 'bottom')) padding.paddingEnd = 42;
26
42
  return padding;
27
43
  };
28
44
 
29
45
  const ChatContainerMessagesList = props => {
30
- useValidateProps(props, ChatContainerMessagesListWithSchema.toTypescript());
46
+ useValidateProps(props, listProps);
31
47
  const {
32
48
  sendingText,
49
+ bannerPosition,
33
50
  sendingMessages,
34
51
  messages,
35
52
  actionRef,
36
53
  onScrollEnds,
37
54
  getMoreMessages,
38
55
  hasMoreItems,
39
- isLoading
56
+ isLoading,
57
+ bannerRef
40
58
  } = props;
59
+ const loadMoreRef = React.useRef(null);
41
60
  const virtualListRef = React.useRef(); // estimateSize should not be really required given what was stated on
42
61
  // https://github.com/tannerlinsley/react-virtual/issues/23
43
62
 
@@ -49,71 +68,88 @@ const ChatContainerMessagesList = props => {
49
68
  size: messages.length,
50
69
  parentRef: virtualListRef,
51
70
  overscan: 15
52
- }, getPadding(hasMoreItems)), {}, {
71
+ }, getPadding({
72
+ hasMoreItems,
73
+ bannerPosition,
74
+ isLoading
75
+ })), {}, {
53
76
  estimateSize: React.useCallback(() => 80, [])
54
77
  }));
55
78
  const onScrollStop = React.useCallback(() => {
79
+ var _virtualListRef$curre;
80
+
56
81
  const {
57
82
  bottom,
58
83
  top
59
- } = virtualListRef.current.getBoundingClientRect();
84
+ } = (_virtualListRef$curre = virtualListRef.current) === null || _virtualListRef$curre === void 0 ? void 0 : _virtualListRef$curre.getBoundingClientRect();
60
85
  const list = virtualListRef.current.querySelectorAll('[data-element="chat-element-list-item"]');
61
86
  const indexList = [];
62
87
  list.forEach(item => {
63
88
  const {
64
- top: itemTop
89
+ top: itemTop,
90
+ bottom: itemBottom
65
91
  } = item.getBoundingClientRect();
66
- if (itemTop < bottom && itemTop > top) indexList.push(item.dataset.index);
92
+ const itemPadding = 10;
93
+ if (itemTop + itemPadding < bottom && itemBottom - itemPadding > top) indexList.push(item.dataset.index);
67
94
  });
68
95
  if (onScrollEnds) onScrollEnds(indexList);
69
96
  return indexList;
70
97
  }, [onScrollEnds]);
71
- const scrollAndFocus = React.useCallback(index => {
72
- vScroll(index, {
73
- align: 'center'
74
- });
98
+ const onlyFocus = React.useCallback(index => {
75
99
  setTimeout(() => {
76
100
  virtualListRef.current.querySelector("[data-index=\"".concat(index, "\"]")).focus();
77
101
  }, 300);
78
- }, [vScroll]);
102
+ }, []);
103
+ const scrollAndFocus = React.useCallback(function (index) {
104
+ let b = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
105
+ align: 'center'
106
+ };
107
+ vScroll(index, b);
108
+ onlyFocus(index);
109
+ }, [onlyFocus, vScroll]);
79
110
  React.useEffect(() => {
80
111
  if (actionRef && actionRef.current) {
81
- actionRef.current.scrollToIndex = (a, b = {
82
- align: 'center'
83
- }) => {
112
+ actionRef.current.scrollToIndex = function (a) {
113
+ let b = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
114
+ align: 'center'
115
+ };
84
116
  vScroll(a, b);
85
117
  };
86
118
 
87
119
  actionRef.current.getMessagesInView = onScrollStop;
88
120
 
89
- actionRef.current.focusToIndex = (a, b = {
90
- align: 'center'
91
- }) => {
92
- scrollAndFocus(a);
121
+ actionRef.current.focusToIndex = function (a) {
122
+ let b = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
123
+ align: 'center'
124
+ };
125
+ scrollAndFocus(a, b);
93
126
  };
94
127
 
95
- actionRef.current.focusToList = (a, b = {
96
- align: 'center'
97
- }) => {
98
- var _virtualListRef$curre;
128
+ actionRef.current.focusToIndexWithoutScroll = onlyFocus;
129
+
130
+ actionRef.current.focusToList = a => {
131
+ var _virtualListRef$curre2;
99
132
 
100
- (_virtualListRef$curre = virtualListRef.current) === null || _virtualListRef$curre === void 0 ? void 0 : _virtualListRef$curre.focus();
133
+ (_virtualListRef$curre2 = virtualListRef.current) === null || _virtualListRef$curre2 === void 0 ? void 0 : _virtualListRef$curre2.focus();
101
134
  };
102
135
  }
103
- }, [actionRef, onScrollStop, scrollAndFocus, vScroll]);
136
+ }, [actionRef, onScrollStop, onlyFocus, scrollAndFocus, vScroll]);
104
137
  const handleKey = React.useCallback(e => {
105
- e.preventDefault();
106
138
  const {
107
139
  key
108
140
  } = e;
109
141
  const index = Number(e.currentTarget.dataset.index);
142
+ e.stopPropagation();
110
143
 
111
144
  switch (key) {
112
145
  case 'ArrowUp':
146
+ e.preventDefault();
113
147
  if (index > 0) virtualListRef.current.querySelector("[data-index=\"".concat(index - 1, "\"]")).focus();
114
148
  break;
115
149
 
116
150
  case 'ArrowDown':
151
+ e.preventDefault();
152
+
117
153
  if (index < messages.length - 1) {
118
154
  virtualListRef.current.querySelector("[data-index=\"".concat(index + 1, "\"]")).focus();
119
155
  }
@@ -121,18 +157,96 @@ const ChatContainerMessagesList = props => {
121
157
  break;
122
158
 
123
159
  case 'Home':
160
+ e.preventDefault();
124
161
  scrollAndFocus(0);
125
162
  break;
126
163
 
127
164
  case 'End':
165
+ e.preventDefault();
128
166
  scrollAndFocus(messages.length - 1);
129
167
  break;
130
168
 
131
169
  case 'Tab':
132
- actionRef.current.focusToComposer();
170
+ e.preventDefault();
171
+
172
+ if (hasMoreItems === 'bottom' && !e.shiftKey) {
173
+ var _loadMoreRef$current;
174
+
175
+ (_loadMoreRef$current = loadMoreRef.current) === null || _loadMoreRef$current === void 0 ? void 0 : _loadMoreRef$current.focus();
176
+ } else if (hasMoreItems === 'top' && e.shiftKey) {
177
+ var _loadMoreRef$current2;
178
+
179
+ (_loadMoreRef$current2 = loadMoreRef.current) === null || _loadMoreRef$current2 === void 0 ? void 0 : _loadMoreRef$current2.focus();
180
+ } else if (!e.shiftKey) {
181
+ if (bannerPosition) {
182
+ bannerRef.current.focusOnWrapper();
183
+ } else {
184
+ actionRef.current.focusToComposer();
185
+ }
186
+ } else {
187
+ var _virtualListRef$curre3;
188
+
189
+ (_virtualListRef$curre3 = virtualListRef.current) === null || _virtualListRef$curre3 === void 0 ? void 0 : _virtualListRef$curre3.focus();
190
+ }
191
+
192
+ break;
193
+ }
194
+ }, [actionRef, hasMoreItems, messages.length, bannerPosition, scrollAndFocus]);
195
+ const handleListKey = React.useCallback(e => {
196
+ const {
197
+ key
198
+ } = e;
199
+
200
+ switch (key) {
201
+ case 'ArrowUp':
202
+ e.preventDefault();
203
+ onlyFocus(onScrollStop().shift());
204
+ break;
205
+
206
+ case 'ArrowDown':
207
+ e.preventDefault();
208
+ onlyFocus(onScrollStop().pop());
209
+ break;
210
+
211
+ case 'Home':
212
+ e.preventDefault();
213
+ scrollAndFocus(0);
214
+ break;
215
+
216
+ case 'End':
217
+ e.preventDefault();
218
+ scrollAndFocus(messages.length - 1);
219
+ break;
220
+
221
+ case 'Tab':
222
+ if (document.activeElement !== loadMoreRef.current) {
223
+ if (hasMoreItems === 'bottom' && !e.shiftKey) {
224
+ var _loadMoreRef$current3;
225
+
226
+ e.preventDefault();
227
+ (_loadMoreRef$current3 = loadMoreRef.current) === null || _loadMoreRef$current3 === void 0 ? void 0 : _loadMoreRef$current3.focus();
228
+ } else if (hasMoreItems === 'top' && e.shiftKey) {
229
+ var _loadMoreRef$current4;
230
+
231
+ e.preventDefault();
232
+ (_loadMoreRef$current4 = loadMoreRef.current) === null || _loadMoreRef$current4 === void 0 ? void 0 : _loadMoreRef$current4.focus();
233
+ } else if (!e.shiftKey) {
234
+ e.preventDefault();
235
+ actionRef.current.focusToComposer();
236
+ }
237
+ } else if (!e.shiftKey) {
238
+ e.preventDefault();
239
+ actionRef.current.focusToComposer();
240
+ } else {
241
+ var _virtualListRef$curre4;
242
+
243
+ e.preventDefault();
244
+ (_virtualListRef$curre4 = virtualListRef.current) === null || _virtualListRef$curre4 === void 0 ? void 0 : _virtualListRef$curre4.focus();
245
+ }
246
+
133
247
  break;
134
248
  }
135
- }, [actionRef, messages.length, scrollAndFocus]);
249
+ }, [actionRef, hasMoreItems, messages.length, onScrollStop, onlyFocus, scrollAndFocus]);
136
250
  React.useEffect(() => {
137
251
  let handlerCallback = null;
138
252
  const localVirtualRef = virtualListRef.current;
@@ -182,7 +296,7 @@ const ChatContainerMessagesList = props => {
182
296
  jsx(ChatBubble, _objectSpread(_objectSpread({}, row), {}, {
183
297
  helpMessage: isSending ? sendingText : row.helpMessage
184
298
  })) : null, row.type === BUBBLE_TYPES.DELIMITER ? /*#__PURE__*/jsx(MessageDelimiter, _objectSpread({}, row)) : null, row.type === BUBBLE_TYPES.SYSTEM ? /*#__PURE__*/jsx(SystemMessage, _objectSpread({}, row)) : null)
185
- }, "ds-chat-bubble-".concat(index));
299
+ }, "ds-chat-bubble-".concat(index, "-").concat(row.dsId, "-").concat(row.type));
186
300
  }), [virtualItems, messages, sendingMessages, handleKey, sendingText]);
187
301
  return /*#__PURE__*/_jsx(StyledWrapper, {}, void 0, /*#__PURE__*/jsx(StyledChatContainerContentList, {
188
302
  "data-testid": "chat-thread-container",
@@ -190,7 +304,7 @@ const ChatContainerMessagesList = props => {
190
304
  "aria-live": "polite",
191
305
  ref: virtualListRef,
192
306
  role: "list",
193
- onKeyDown: handleKey,
307
+ onKeyDown: handleListKey,
194
308
  children: /*#__PURE__*/_jsx(StyledChatContainerContentListScroll, {
195
309
  height: "".concat(totalSize, "px")
196
310
  }, void 0, render, !isLoading && hasMoreItems ? /*#__PURE__*/_jsx(StyledLoadMoreContainer, {
@@ -198,7 +312,9 @@ const ChatContainerMessagesList = props => {
198
312
  }, void 0, /*#__PURE__*/_jsx(DSButton, {
199
313
  labelText: "Load More",
200
314
  buttonType: "secondary",
201
- onClick: getMoreMessages
315
+ onClick: getMoreMessages,
316
+ "data-testid": "ds-chat-load-more-btn",
317
+ innerRef: loadMoreRef
202
318
  })) : null)
203
319
  }));
204
320
  };
@@ -1,7 +1,7 @@
1
1
  import { useValidateTypescriptPropTypes } from '@elliemae/ds-props-helpers';
2
2
 
3
- const useValidateProps = (props, schema) => {
4
- useValidateTypescriptPropTypes(props, schema);
3
+ const useValidateProps = (props, propTypes) => {
4
+ useValidateTypescriptPropTypes(props, propTypes);
5
5
  };
6
6
 
7
7
  export { useValidateProps };
@@ -7,11 +7,13 @@ const defaultProps = {
7
7
  onScrollEnds: () => [],
8
8
  getMoreMessages: noop,
9
9
  sendingMessages: undefined,
10
- sendingText: 'Sending'
10
+ sendingText: 'Sending',
11
+ bannerPosition: undefined,
12
+ bannerRef: undefined
11
13
  };
12
14
  const listProps = {
13
15
  messages: PropTypes.arrayOf(PropTypes.object).description('The array of out-of-the-box items you want to render inside the ChatMessageList').defaultValue(defaultProps.messages),
14
- actionRef: PropTypes.object.description("Reference: \n { \n scrollToIndex: (n: number) => void,\n getMessagesInView: () => number[],\n focusToIndex: (n: number) => void,\n focusToList: () => void,\n focusToComposer: () => void,\n focusToSend: () => void,\n }\n \n ").defaultValue(defaultProps.actionRef),
16
+ actionRef: PropTypes.object.description("Reference: \n { \n scrollToIndex: (n: number) => void,\n getMessagesInView: () => number[],\n focusToIndex: (n: number) => void,\n focusToIndexWithoutScroll: (n: number) => void,\n focusToList: () => void,\n focusToComposer: () => void,\n focusToSend: () => void,\n }\n \n ").defaultValue(defaultProps.actionRef),
15
17
  onScrollEnds: PropTypes.func.description("Called when the scroll stops."),
16
18
  isLoading: PropTypes.oneOf(['top', 'bottom', undefined]).description('Keeps the load more space while the loading is in progress'),
17
19
  hasMoreItems: PropTypes.oneOf(['top', 'bottom', undefined]).description('Show Load More button at the `top` or `bottom` of the container'),
@@ -8,7 +8,7 @@ const StyledWrapper = /*#__PURE__*/styled.div.withConfig({
8
8
 
9
9
  const StyledWrapperListItem = /*#__PURE__*/styled.div.withConfig({
10
10
  componentId: "sc-1jhprzr-1"
11
- })(["min-width:100%;", ";", ";", ";"], props => props.type === BUBBLE_TYPES.SENDER && "padding: ".concat(props.theme.space.xxs, " ").concat(props.theme.space.xxs, " 0px 13%\n "), props => props.type === BUBBLE_TYPES.RECIPIENT && "padding: ".concat(props.theme.space.xxs, " 13% ").concat(props.theme.space.xxs, "\n ").concat(props.theme.space.xxs, "\n "), props => props.type === BUBBLE_TYPES.SYSTEM && "padding: ".concat(props.theme.space.xxs, " 0px\n "));
11
+ })(["min-width:100%;", ";", ";", ";"], props => props.type === BUBBLE_TYPES.SENDER && "padding: ".concat(props.theme.space.xxs, " ").concat(props.theme.space.xxs, " ").concat(props.theme.space.xxs, " 13%\n "), props => props.type === BUBBLE_TYPES.RECIPIENT && "padding: ".concat(props.theme.space.xxs, " 13% ").concat(props.theme.space.xxs, "\n ").concat(props.theme.space.xxs, "\n "), props => props.type === BUBBLE_TYPES.SYSTEM && "padding: ".concat(props.theme.space.xxs, " 0px\n "));
12
12
  const StyledChatContainerContentList = /*#__PURE__*/styled.div.withConfig({
13
13
  componentId: "sc-1jhprzr-2"
14
14
  })(["width:100%;height:100%;padding:", ";padding-bottom:0;overflow-y:auto;overflow-x:hidden;"], props => props.theme.space.xs);
@@ -20,6 +20,6 @@ const StyledLoadMoreContainer = /*#__PURE__*/styled.div.withConfig({
20
20
  })(["position:absolute;width:calc(100% + ", ");padding:", ";left:-", ";right:-", ";display:grid;justify-content:center;align-items:center;", " ", ""], props => props.theme.space.xxs, props => props.theme.space.xxs, props => props.theme.space.xxs, props => props.theme.space.xxs, props => props.hasMoreItems === 'top' ? 'top: 0;' : '', props => props.hasMoreItems === 'bottom' ? 'bottom: 0;' : '');
21
21
  const ListItem = /*#__PURE__*/styled.div.withConfig({
22
22
  componentId: "sc-1jhprzr-5"
23
- })(["&:focus,&:focus-visible{outline:none;&:after{content:'';display:block;position:absolute;pointer-events:none;background:rgba(255,255,255,0.5);width:calc(100% + ", ");height:100%;}}"], props => props.theme.space.xxs);
23
+ })(["&:focus,&:focus-visible{outline:none;&:after{content:'';display:block;position:absolute;top:0;left:-", ";pointer-events:none;border-radius:2px;background-color:#ebedf0;width:calc(100% + 24px);border:2px solid #1e79c2;height:100%;z-index:-1;}}"], props => props.theme.space.xxs);
24
24
 
25
25
  export { ListItem, StyledChatContainerContentList, StyledChatContainerContentListScroll, StyledLoadMoreContainer, StyledWrapper, StyledWrapperListItem };
@@ -3,7 +3,7 @@ import 'core-js/modules/web.url.to-json.js';
3
3
  import { useMemo } from 'react';
4
4
  import { describe } from 'react-desc';
5
5
  import { Grid } from '@elliemae/ds-grid';
6
- import Communications from '@elliemae/ds-icons/Communications';
6
+ import { Communications } from '@elliemae/ds-icons';
7
7
  import { emptyStateProps, defaultProps } from './props.js';
8
8
  import { MessageWrapper, ChatEmptyStateWrapper } from './styled.js';
9
9
  import { useValidateProps } from './config/useValidateProps.js';