@elliemae/ds-chat 2.0.0-next.7 → 2.0.0-rc.10

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 (110) 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 +2 -2
  9. package/cjs/parts/chat-container/ChatContainer.js +56 -14
  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 +137 -32
  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-container/ChatContainer.js +54 -12
  40. package/esm/parts/chat-container/config/useValidateProps.js +2 -2
  41. package/esm/parts/chat-container/props.js +7 -3
  42. package/esm/parts/chat-container/styled.js +1 -1
  43. package/esm/parts/chat-container-header/ChatContainerHeader.js +3 -2
  44. package/esm/parts/chat-container-header/helpers.js +23 -0
  45. package/esm/parts/chat-container-header/styled.js +6 -6
  46. package/esm/parts/chat-container-messages-list/ChatContainerMessagesList.js +135 -30
  47. package/esm/parts/chat-container-messages-list/config/useValidateProps.js +2 -2
  48. package/esm/parts/chat-container-messages-list/props.js +4 -2
  49. package/esm/parts/chat-container-messages-list/styled.js +2 -2
  50. package/esm/parts/chat-empty-state/ChatEmptyState.js +1 -1
  51. package/esm/parts/chat-sidebar/SideBarChat.js +57 -28
  52. package/esm/parts/chat-sidebar/props.js +11 -1
  53. package/esm/parts/chat-sidebar/styled.js +2 -2
  54. package/esm/parts/floating-button/FloatingButton.js +18 -9
  55. package/esm/parts/floating-button/props.js +6 -2
  56. package/esm/parts/sidebar-button/SidebarButton.js +5 -0
  57. package/esm/parts/sidebar-button/props.js +1 -1
  58. package/esm/parts/system-message/style.js +1 -1
  59. package/esm/parts/tile-button/TileButton.js +31 -26
  60. package/esm/parts/tile-button/props.js +11 -3
  61. package/esm/parts/tile-button/styled.js +11 -6
  62. package/package.json +28 -6
  63. package/types/ChatDataTestids.d.ts +21 -0
  64. package/types/parts/bubble/ChatBubble.d.ts +18 -5
  65. package/types/parts/bubble/props.d.ts +13 -4
  66. package/types/parts/bubble/styled.d.ts +9 -9
  67. package/types/parts/bubble/tests/ChatBubble.test.d.ts +1 -0
  68. package/types/parts/chat-card/ChatCard.d.ts +24 -4
  69. package/types/parts/chat-card/props.d.ts +19 -3
  70. package/types/parts/chat-card/styled.d.ts +6 -6
  71. package/types/parts/chat-composer/ChatContainerComposer.d.ts +48 -13
  72. package/types/parts/chat-composer/styled.d.ts +117 -24
  73. package/types/parts/chat-composer/tests/ChatContainerComposer.test.d.ts +1 -0
  74. package/types/parts/chat-container/ChatContainer.d.ts +54 -13
  75. package/types/parts/chat-container/config/useValidateProps.d.ts +1 -1
  76. package/types/parts/chat-container/styled.d.ts +4 -4
  77. package/types/parts/chat-container/tests/ChatContainer.test.d.ts +1 -0
  78. package/types/parts/chat-container-header/helpers.d.ts +6 -0
  79. package/types/parts/chat-container-header/styled.d.ts +3 -3
  80. package/types/parts/chat-container-messages-list/ChatContainerMessagesList.d.ts +39 -9
  81. package/types/parts/chat-container-messages-list/config/useValidateProps.d.ts +1 -1
  82. package/types/parts/chat-container-messages-list/styled.d.ts +7 -7
  83. package/types/parts/chat-empty-state/ChatEmptyState.d.ts +12 -3
  84. package/types/parts/chat-empty-state/props.d.ts +7 -2
  85. package/types/parts/chat-empty-state/styled.d.ts +2 -2
  86. package/types/parts/chat-sidebar/SideBarChat.d.ts +39 -9
  87. package/types/parts/chat-sidebar/props.d.ts +39 -9
  88. package/types/parts/chat-sidebar/styled.d.ts +2 -2
  89. package/types/parts/floating-button/FloatingButton.d.ts +11 -2
  90. package/types/parts/floating-button/props.d.ts +13 -1
  91. package/types/parts/floating-button/styled.d.ts +1 -1
  92. package/types/parts/floating-container/styled.d.ts +1 -2
  93. package/types/parts/grid-influencer/styled.d.ts +2 -2
  94. package/types/parts/message-delimiter/MessageDelimiter.d.ts +21 -4
  95. package/types/parts/message-delimiter/props.d.ts +16 -3
  96. package/types/parts/message-delimiter/styled.d.ts +3 -3
  97. package/types/parts/sidebar-button/SidebarButton.d.ts +16 -5
  98. package/types/parts/sidebar-button/props.d.ts +16 -5
  99. package/types/parts/sidebar-button/styled.d.ts +3 -3
  100. package/types/parts/system-message/SystemMessage.d.ts +12 -2
  101. package/types/parts/system-message/props.d.ts +7 -1
  102. package/types/parts/system-message/style.d.ts +5 -5
  103. package/types/parts/tile-button/TileButton.d.ts +66 -17
  104. package/types/parts/tile-button/props.d.ts +64 -16
  105. package/types/parts/tile-button/styled.d.ts +9 -8
  106. package/types/prop-types.d.ts +28 -9
  107. package/types/tests/utils.d.ts +27 -0
  108. package/types/parts/chat-composer/props.d.ts +0 -35
  109. package/types/parts/chat-container/props.d.ts +0 -39
  110. 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,21 @@ 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
42
  autoScroll,
37
43
  device,
38
44
  isLoading,
39
45
  hasMoreItems,
40
- getMoreMessages
46
+ getMoreMessages,
47
+ ariaLabel
41
48
  } = props;
49
+ const bannerRef = React.useRef({
50
+ focusOnWrapper: noop,
51
+ focusOnLink: noop
52
+ });
42
53
  const containerRef = React.useRef();
43
54
  const listRef = React.useRef({
44
55
  scrollToIndex: noop
@@ -53,14 +64,38 @@ const ChatContainer = props => {
53
64
  }
54
65
  }, [onComposerResize, messagesListProps.messages, autoScroll]);
55
66
  const Container = device === 'desktop' ? StyledChatContainerContent : MobileStyledChatContainerContent;
67
+ const handleTabs = React.useCallback(e => {
68
+ if (onComposerKeyDown) onComposerKeyDown(e);else {
69
+ const ref = actionRef || listRef;
70
+
71
+ if (e.shiftKey && e.key === 'Tab') {
72
+ e.preventDefault();
73
+
74
+ if (['top', 'bottom'].includes(bannerPosition)) {
75
+ bannerRef.current.focusOnWrapper();
76
+ } else {
77
+ const last = ref.current.getMessagesInView().pop();
78
+ ref.current.focusToIndexWithoutScroll(last);
79
+ }
80
+ }
81
+ }
82
+ }, [actionRef, onComposerKeyDown, bannerPosition]); // Map actions
83
+
84
+ React.useEffect(() => {
85
+ if (actionRef && actionRef.current) {
86
+ actionRef.current.focusOnBanner = bannerRef.current.focusOnWrapper;
87
+ actionRef.current.focusOnBannerLink = bannerRef.current.focusOnLink;
88
+ }
89
+ }, [actionRef]);
56
90
  return /*#__PURE__*/jsxs(Container, {
57
91
  rows: ['minmax(0, 1fr)', 'auto'],
58
92
  ref: containerRef,
59
93
  role: "region",
60
- "aria-label": "chat window",
94
+ "aria-label": ariaLabel,
61
95
  children: [/*#__PURE__*/_jsx("div", {
62
96
  style: {
63
- position: 'relative'
97
+ position: 'relative',
98
+ overflow: 'hidden'
64
99
  }
65
100
  }, void 0, /*#__PURE__*/jsx(ChatContainerMessagesList, _objectSpread(_objectSpread({}, messagesListProps), {}, {
66
101
  actionRef: actionRef || listRef,
@@ -68,13 +103,17 @@ const ChatContainer = props => {
68
103
  hasMoreItems: hasMoreItems,
69
104
  getMoreMessages: getMoreMessages,
70
105
  sendingMessages: sendingMessages,
71
- sendingText: sendingText
106
+ sendingText: sendingText,
107
+ bannerPosition: bannerPosition,
108
+ bannerRef: bannerRef
72
109
  })), ['top', 'bottom'].includes(isLoading) ? /*#__PURE__*/_jsx(StyledIndicatorContainer, {
73
- isLoading: isLoading
110
+ isLoading: isLoading,
111
+ "data-testid": "ds-chat-loading-indicator"
74
112
  }, void 0, _DSIndeterminateProgr || (_DSIndeterminateProgr = /*#__PURE__*/_jsx(DSIndeterminateProgressIndicator, {
75
113
  processing: true,
76
114
  title: "Loading"
77
- }))) : null, ['top', 'bottom'].includes(bannerPosition) ? /*#__PURE__*/_jsx(StyledIndicatorContainer, {
115
+ }))) : null, /*#__PURE__*/_jsx(StyledIndicatorContainer, {
116
+ linear: true,
78
117
  isLoading: bannerPosition
79
118
  }, void 0, /*#__PURE__*/_jsx(DSBanner, {
80
119
  showCloseButton: true,
@@ -88,15 +127,18 @@ const ChatContainer = props => {
88
127
  },
89
128
  onClose: bannerProps.onClose,
90
129
  isOpen: true,
91
- type: BANNER_TYPES.INFO
92
- })) : null, sendingMessages && sendingMessages.length > 0 ? _StyledIndicatorConta || (_StyledIndicatorConta = /*#__PURE__*/_jsx(StyledIndicatorContainer, {
130
+ type: bannerProps.type || BANNER_TYPES.INFO,
131
+ actionRef: bannerRef
132
+ })), sendingMessages && sendingMessages.length > 0 ? _StyledIndicatorConta || (_StyledIndicatorConta = /*#__PURE__*/_jsx(StyledIndicatorContainer, {
93
133
  isLoading: "bottom",
94
- linear: true
134
+ linear: true,
135
+ "data-testid": "ds-chat-loading-indicator"
95
136
  }, void 0, /*#__PURE__*/_jsx(DSIndeterminateProgressIndicator, {
96
137
  processing: true,
97
138
  title: "Loading",
98
139
  lineOnly: true
99
140
  }))) : null), /*#__PURE__*/jsx(ChatComposer, _objectSpread(_objectSpread({}, composerProps), {}, {
141
+ onKeyDown: handleTabs,
100
142
  onResize: onResize,
101
143
  maxHeight: (((_containerRef$current = containerRef.current) === null || _containerRef$current === void 0 ? void 0 : _containerRef$current.clientHeight) || 0) / 2,
102
144
  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,18 @@
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.for-each.js';
6
+ import 'core-js/modules/esnext.iterator.constructor.js';
7
+ import 'core-js/modules/esnext.iterator.for-each.js';
8
+ import 'core-js/modules/esnext.async-iterator.map.js';
9
+ import 'core-js/modules/esnext.iterator.map.js';
10
+ import 'core-js/modules/esnext.async-iterator.find.js';
11
+ import 'core-js/modules/esnext.iterator.find.js';
3
12
  import React from 'react';
4
13
  import { describe } from 'react-desc';
5
14
  import { useVirtual } from 'react-virtual';
6
- import DSButton from '@elliemae/ds-basic/Button';
15
+ import DSButton from '@elliemae/ds-button';
7
16
  import { ChatBubble } from '../bubble/ChatBubble.js';
8
17
  import { BUBBLE_TYPES } from '../bubble/props.js';
9
18
  import { MessageDelimiter } from '../message-delimiter/MessageDelimiter.js';
@@ -27,17 +36,20 @@ const getPadding = hasMoreItems => {
27
36
  };
28
37
 
29
38
  const ChatContainerMessagesList = props => {
30
- useValidateProps(props, ChatContainerMessagesListWithSchema.toTypescript());
39
+ useValidateProps(props, listProps);
31
40
  const {
32
41
  sendingText,
42
+ bannerPosition,
33
43
  sendingMessages,
34
44
  messages,
35
45
  actionRef,
36
46
  onScrollEnds,
37
47
  getMoreMessages,
38
48
  hasMoreItems,
39
- isLoading
49
+ isLoading,
50
+ bannerRef
40
51
  } = props;
52
+ const loadMoreRef = React.useRef(null);
41
53
  const virtualListRef = React.useRef(); // estimateSize should not be really required given what was stated on
42
54
  // https://github.com/tannerlinsley/react-virtual/issues/23
43
55
 
@@ -53,67 +65,80 @@ const ChatContainerMessagesList = props => {
53
65
  estimateSize: React.useCallback(() => 80, [])
54
66
  }));
55
67
  const onScrollStop = React.useCallback(() => {
68
+ var _virtualListRef$curre;
69
+
56
70
  const {
57
71
  bottom,
58
72
  top
59
- } = virtualListRef.current.getBoundingClientRect();
73
+ } = (_virtualListRef$curre = virtualListRef.current) === null || _virtualListRef$curre === void 0 ? void 0 : _virtualListRef$curre.getBoundingClientRect();
60
74
  const list = virtualListRef.current.querySelectorAll('[data-element="chat-element-list-item"]');
61
75
  const indexList = [];
62
76
  list.forEach(item => {
63
77
  const {
64
- top: itemTop
78
+ top: itemTop,
79
+ bottom: itemBottom
65
80
  } = item.getBoundingClientRect();
66
- if (itemTop < bottom && itemTop > top) indexList.push(item.dataset.index);
81
+ const itemPadding = 10;
82
+ if (itemTop + itemPadding < bottom && itemBottom - itemPadding > top) indexList.push(item.dataset.index);
67
83
  });
68
84
  if (onScrollEnds) onScrollEnds(indexList);
69
85
  return indexList;
70
86
  }, [onScrollEnds]);
71
- const scrollAndFocus = React.useCallback(index => {
72
- vScroll(index, {
73
- align: 'center'
74
- });
87
+ const onlyFocus = React.useCallback(index => {
75
88
  setTimeout(() => {
76
89
  virtualListRef.current.querySelector("[data-index=\"".concat(index, "\"]")).focus();
77
90
  }, 300);
78
- }, [vScroll]);
91
+ }, []);
92
+ const scrollAndFocus = React.useCallback(function (index) {
93
+ let b = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
94
+ align: 'center'
95
+ };
96
+ vScroll(index, b);
97
+ onlyFocus(index);
98
+ }, [onlyFocus, vScroll]);
79
99
  React.useEffect(() => {
80
100
  if (actionRef && actionRef.current) {
81
- actionRef.current.scrollToIndex = (a, b = {
82
- align: 'center'
83
- }) => {
101
+ actionRef.current.scrollToIndex = function (a) {
102
+ let b = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
103
+ align: 'center'
104
+ };
84
105
  vScroll(a, b);
85
106
  };
86
107
 
87
108
  actionRef.current.getMessagesInView = onScrollStop;
88
109
 
89
- actionRef.current.focusToIndex = (a, b = {
90
- align: 'center'
91
- }) => {
92
- scrollAndFocus(a);
110
+ actionRef.current.focusToIndex = function (a) {
111
+ let b = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
112
+ align: 'center'
113
+ };
114
+ scrollAndFocus(a, b);
93
115
  };
94
116
 
95
- actionRef.current.focusToList = (a, b = {
96
- align: 'center'
97
- }) => {
98
- var _virtualListRef$curre;
117
+ actionRef.current.focusToIndexWithoutScroll = onlyFocus;
99
118
 
100
- (_virtualListRef$curre = virtualListRef.current) === null || _virtualListRef$curre === void 0 ? void 0 : _virtualListRef$curre.focus();
119
+ actionRef.current.focusToList = a => {
120
+ var _virtualListRef$curre2;
121
+
122
+ (_virtualListRef$curre2 = virtualListRef.current) === null || _virtualListRef$curre2 === void 0 ? void 0 : _virtualListRef$curre2.focus();
101
123
  };
102
124
  }
103
- }, [actionRef, onScrollStop, scrollAndFocus, vScroll]);
125
+ }, [actionRef, onScrollStop, onlyFocus, scrollAndFocus, vScroll]);
104
126
  const handleKey = React.useCallback(e => {
105
- e.preventDefault();
106
127
  const {
107
128
  key
108
129
  } = e;
109
130
  const index = Number(e.currentTarget.dataset.index);
131
+ e.stopPropagation();
110
132
 
111
133
  switch (key) {
112
134
  case 'ArrowUp':
135
+ e.preventDefault();
113
136
  if (index > 0) virtualListRef.current.querySelector("[data-index=\"".concat(index - 1, "\"]")).focus();
114
137
  break;
115
138
 
116
139
  case 'ArrowDown':
140
+ e.preventDefault();
141
+
117
142
  if (index < messages.length - 1) {
118
143
  virtualListRef.current.querySelector("[data-index=\"".concat(index + 1, "\"]")).focus();
119
144
  }
@@ -121,18 +146,96 @@ const ChatContainerMessagesList = props => {
121
146
  break;
122
147
 
123
148
  case 'Home':
149
+ e.preventDefault();
150
+ scrollAndFocus(0);
151
+ break;
152
+
153
+ case 'End':
154
+ e.preventDefault();
155
+ scrollAndFocus(messages.length - 1);
156
+ break;
157
+
158
+ case 'Tab':
159
+ e.preventDefault();
160
+
161
+ if (hasMoreItems === 'bottom' && !e.shiftKey) {
162
+ var _loadMoreRef$current;
163
+
164
+ (_loadMoreRef$current = loadMoreRef.current) === null || _loadMoreRef$current === void 0 ? void 0 : _loadMoreRef$current.focus();
165
+ } else if (hasMoreItems === 'top' && e.shiftKey) {
166
+ var _loadMoreRef$current2;
167
+
168
+ (_loadMoreRef$current2 = loadMoreRef.current) === null || _loadMoreRef$current2 === void 0 ? void 0 : _loadMoreRef$current2.focus();
169
+ } else if (!e.shiftKey) {
170
+ if (bannerPosition) {
171
+ bannerRef.current.focusOnWrapper();
172
+ } else {
173
+ actionRef.current.focusToComposer();
174
+ }
175
+ } else {
176
+ var _virtualListRef$curre3;
177
+
178
+ (_virtualListRef$curre3 = virtualListRef.current) === null || _virtualListRef$curre3 === void 0 ? void 0 : _virtualListRef$curre3.focus();
179
+ }
180
+
181
+ break;
182
+ }
183
+ }, [actionRef, hasMoreItems, messages.length, bannerPosition, scrollAndFocus]);
184
+ const handleListKey = React.useCallback(e => {
185
+ const {
186
+ key
187
+ } = e;
188
+
189
+ switch (key) {
190
+ case 'ArrowUp':
191
+ e.preventDefault();
192
+ onlyFocus(onScrollStop().shift());
193
+ break;
194
+
195
+ case 'ArrowDown':
196
+ e.preventDefault();
197
+ onlyFocus(onScrollStop().pop());
198
+ break;
199
+
200
+ case 'Home':
201
+ e.preventDefault();
124
202
  scrollAndFocus(0);
125
203
  break;
126
204
 
127
205
  case 'End':
206
+ e.preventDefault();
128
207
  scrollAndFocus(messages.length - 1);
129
208
  break;
130
209
 
131
210
  case 'Tab':
132
- actionRef.current.focusToComposer();
211
+ if (document.activeElement !== loadMoreRef.current) {
212
+ if (hasMoreItems === 'bottom' && !e.shiftKey) {
213
+ var _loadMoreRef$current3;
214
+
215
+ e.preventDefault();
216
+ (_loadMoreRef$current3 = loadMoreRef.current) === null || _loadMoreRef$current3 === void 0 ? void 0 : _loadMoreRef$current3.focus();
217
+ } else if (hasMoreItems === 'top' && e.shiftKey) {
218
+ var _loadMoreRef$current4;
219
+
220
+ e.preventDefault();
221
+ (_loadMoreRef$current4 = loadMoreRef.current) === null || _loadMoreRef$current4 === void 0 ? void 0 : _loadMoreRef$current4.focus();
222
+ } else if (!e.shiftKey) {
223
+ e.preventDefault();
224
+ actionRef.current.focusToComposer();
225
+ }
226
+ } else if (!e.shiftKey) {
227
+ e.preventDefault();
228
+ actionRef.current.focusToComposer();
229
+ } else {
230
+ var _virtualListRef$curre4;
231
+
232
+ e.preventDefault();
233
+ (_virtualListRef$curre4 = virtualListRef.current) === null || _virtualListRef$curre4 === void 0 ? void 0 : _virtualListRef$curre4.focus();
234
+ }
235
+
133
236
  break;
134
237
  }
135
- }, [actionRef, messages.length, scrollAndFocus]);
238
+ }, [actionRef, hasMoreItems, messages.length, onScrollStop, onlyFocus, scrollAndFocus]);
136
239
  React.useEffect(() => {
137
240
  let handlerCallback = null;
138
241
  const localVirtualRef = virtualListRef.current;
@@ -182,7 +285,7 @@ const ChatContainerMessagesList = props => {
182
285
  jsx(ChatBubble, _objectSpread(_objectSpread({}, row), {}, {
183
286
  helpMessage: isSending ? sendingText : row.helpMessage
184
287
  })) : 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));
288
+ }, "ds-chat-bubble-".concat(index, "-").concat(row.dsId, "-").concat(row.type));
186
289
  }), [virtualItems, messages, sendingMessages, handleKey, sendingText]);
187
290
  return /*#__PURE__*/_jsx(StyledWrapper, {}, void 0, /*#__PURE__*/jsx(StyledChatContainerContentList, {
188
291
  "data-testid": "chat-thread-container",
@@ -190,7 +293,7 @@ const ChatContainerMessagesList = props => {
190
293
  "aria-live": "polite",
191
294
  ref: virtualListRef,
192
295
  role: "list",
193
- onKeyDown: handleKey,
296
+ onKeyDown: handleListKey,
194
297
  children: /*#__PURE__*/_jsx(StyledChatContainerContentListScroll, {
195
298
  height: "".concat(totalSize, "px")
196
299
  }, void 0, render, !isLoading && hasMoreItems ? /*#__PURE__*/_jsx(StyledLoadMoreContainer, {
@@ -198,7 +301,9 @@ const ChatContainerMessagesList = props => {
198
301
  }, void 0, /*#__PURE__*/_jsx(DSButton, {
199
302
  labelText: "Load More",
200
303
  buttonType: "secondary",
201
- onClick: getMoreMessages
304
+ onClick: getMoreMessages,
305
+ "data-testid": "ds-chat-load-more-btn",
306
+ innerRef: loadMoreRef
202
307
  })) : null)
203
308
  }));
204
309
  };
@@ -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';