botframework-webchat-fluent-theme 4.17.1 → 4.18.1-hotfix.20260127.b53acdf

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 (96) hide show
  1. package/dist/botframework-webchat-fluent-theme.css.map +1 -1
  2. package/dist/botframework-webchat-fluent-theme.d.mts +23 -5
  3. package/dist/botframework-webchat-fluent-theme.d.ts +23 -5
  4. package/dist/botframework-webchat-fluent-theme.development.css.map +1 -1
  5. package/dist/botframework-webchat-fluent-theme.development.js +11 -1
  6. package/dist/botframework-webchat-fluent-theme.development.js.map +1 -1
  7. package/dist/botframework-webchat-fluent-theme.js +1 -1
  8. package/dist/botframework-webchat-fluent-theme.js.map +1 -1
  9. package/dist/botframework-webchat-fluent-theme.mjs +1 -1
  10. package/dist/botframework-webchat-fluent-theme.mjs.map +1 -1
  11. package/dist/botframework-webchat-fluent-theme.production.min.css.map +1 -1
  12. package/dist/botframework-webchat-fluent-theme.production.min.js +11 -1
  13. package/dist/botframework-webchat-fluent-theme.production.min.js.map +1 -1
  14. package/package.json +27 -26
  15. package/src/bundle.ts +2 -0
  16. package/src/components/activity/ActivityDecorator.module.css +432 -0
  17. package/src/components/activity/ActivityDecorator.tsx +26 -0
  18. package/src/components/activity/ActivityLoader.module.css +10 -0
  19. package/src/components/activity/ActivityLoader.tsx +21 -0
  20. package/src/components/activity/CopilotMessageHeader.module.css +38 -0
  21. package/src/components/activity/CopilotMessageHeader.tsx +49 -0
  22. package/src/components/activity/index.ts +2 -0
  23. package/src/components/activity/private/isAIGeneratedActivity.ts +5 -0
  24. package/src/components/activity/private/useActivityAuthor.ts +16 -0
  25. package/src/components/activity/private/useActivityStyleOptions.ts +19 -0
  26. package/src/components/assets/AssetComposer.tsx +37 -0
  27. package/src/components/assets/AssetName.ts +1 -0
  28. package/src/components/assets/SlidingDots.tsx +61 -0
  29. package/src/components/assets/private/Context.ts +24 -0
  30. package/src/components/assets/private/useAssetURL.ts +12 -0
  31. package/src/components/assets/private/useContext.ts +8 -0
  32. package/src/components/dropZone/DropZone.module.css +3 -4
  33. package/src/components/dropZone/DropZone.tsx +41 -6
  34. package/src/components/linerActivity/index.ts +2 -0
  35. package/src/components/linerActivity/private/LinerActivity.tsx +20 -0
  36. package/src/components/linerActivity/private/LinerMessageActivity.module.css +28 -0
  37. package/src/components/linerActivity/private/isLinerMessageActivity.ts +7 -0
  38. package/src/components/preChatActivity/PreChatMessageActivity.module.css +56 -0
  39. package/src/components/preChatActivity/PreChatMessageActivity.tsx +60 -0
  40. package/src/components/preChatActivity/StarterPromptsCardAction.module.css +126 -0
  41. package/src/components/preChatActivity/StarterPromptsCardAction.tsx +75 -0
  42. package/src/components/preChatActivity/StarterPromptsToolbar.module.css +18 -0
  43. package/src/components/preChatActivity/StarterPromptsToolbar.tsx +47 -0
  44. package/src/components/preChatActivity/index.tsx +2 -0
  45. package/src/components/preChatActivity/isPreChatMessageActivity.ts +7 -0
  46. package/src/components/sendBox/Attachments.module.css +1 -1
  47. package/src/components/sendBox/Attachments.tsx +5 -4
  48. package/src/components/sendBox/ErrorMessage.tsx +15 -4
  49. package/src/components/sendBox/SendBox.module.css +21 -6
  50. package/src/components/sendBox/SendBox.tsx +95 -56
  51. package/src/components/sendBox/TextArea.module.css +27 -8
  52. package/src/components/sendBox/TextArea.tsx +60 -31
  53. package/src/components/sendBox/Toolbar.module.css +15 -7
  54. package/src/components/sendBox/Toolbar.tsx +17 -7
  55. package/src/components/sendBox/index.tsx +1 -1
  56. package/src/components/sendBox/private/useSubmitError.ts +17 -4
  57. package/src/components/sendBox/private/useTranscriptNavigation.ts +53 -0
  58. package/src/components/suggestedActions/AccessibleButton.tsx +15 -13
  59. package/src/components/suggestedActions/SuggestedAction.module.css +14 -13
  60. package/src/components/suggestedActions/SuggestedAction.tsx +7 -4
  61. package/src/components/suggestedActions/SuggestedActions.module.css +2 -3
  62. package/src/components/suggestedActions/SuggestedActions.tsx +49 -46
  63. package/src/components/telephoneKeypad/private/Button.module.css +2 -3
  64. package/src/components/telephoneKeypad/private/Button.tsx +1 -5
  65. package/src/components/telephoneKeypad/private/TelephoneKeypad.module.css +0 -1
  66. package/src/components/telephoneKeypad/private/TelephoneKeypad.tsx +4 -8
  67. package/src/components/telephoneKeypad/types.ts +1 -1
  68. package/src/components/theme/Theme.module.css +665 -15
  69. package/src/components/theme/Theme.tsx +4 -3
  70. package/src/components/typingIndicator/SlidingDotsTypingIndicator.module.css +12 -0
  71. package/src/components/typingIndicator/SlidingDotsTypingIndicator.tsx +19 -0
  72. package/src/env.d.ts +1 -7
  73. package/src/external.umd/botframework-webchat-api/decorator.ts +1 -0
  74. package/src/external.umd/botframework-webchat-component/decorator.ts +1 -0
  75. package/src/external.umd/botframework-webchat-component/index.ts +5 -0
  76. package/src/external.umd/botframework-webchat-component/internal.ts +1 -0
  77. package/src/icons/AddDocumentIcon.tsx +8 -16
  78. package/src/icons/AttachmentIcon.tsx +8 -16
  79. package/src/icons/InfoSmallIcon.tsx +7 -15
  80. package/src/icons/SendIcon.tsx +7 -15
  81. package/src/icons/TelephoneKeypadIcon.tsx +7 -15
  82. package/src/index.ts +2 -4
  83. package/src/private/FluentThemeProvider.tsx +91 -10
  84. package/src/private/VariantComposer.ts +29 -0
  85. package/src/private/createComposer.tsx +16 -0
  86. package/src/private/useVariants.ts +7 -0
  87. package/src/styles/createStyles.ts +5 -0
  88. package/src/styles/index.ts +3 -2
  89. package/src/styles/useStyles.ts +2 -19
  90. package/src/styles/useVariantClassName.ts +16 -0
  91. package/src/testIds.ts +3 -0
  92. package/src/tsconfig.json +12 -10
  93. package/src/types/PropsOf.ts +2 -5
  94. package/src/external.umd/botframework-webchat-component.ts +0 -4
  95. package/src/styles/injectStyle.ts +0 -9
  96. /package/src/external.umd/{botframework-webchat-api.ts → botframework-webchat-api/index.ts} +0 -0
@@ -0,0 +1,75 @@
1
+ import { Components, hooks } from 'botframework-webchat-component';
2
+ import { type DirectLineCardAction } from 'botframework-webchat-core';
3
+ import cx from 'classnames';
4
+ import React, { memo, useCallback, useMemo } from 'react';
5
+ import { useRefFrom } from 'use-ref-from';
6
+ import { useStyles } from '../../styles/index.js';
7
+ import testIds from '../../testIds.js';
8
+ import styles from './StarterPromptsCardAction.module.css';
9
+
10
+ const { useFocus, useRenderMarkdownAsHTML, useSendBoxValue, useUIState } = hooks;
11
+ const { MonochromeImageMasker } = Components;
12
+
13
+ type Props = Readonly<{
14
+ className?: string | undefined;
15
+ messageBackAction?: (DirectLineCardAction & { type: 'messageBack' }) | undefined;
16
+ }>;
17
+
18
+ const StarterPromptsCardAction = ({ className, messageBackAction }: Props) => {
19
+ const [_, setSendBoxValue] = useSendBoxValue();
20
+ const [uiState] = useUIState();
21
+ const classNames = useStyles(styles);
22
+ const focus = useFocus();
23
+ const inputTextRef = useRefFrom(messageBackAction?.displayText || messageBackAction?.text || '');
24
+ const renderMarkdownAsHTML = useRenderMarkdownAsHTML('message activity');
25
+ const subtitleHTML = useMemo<{ __html: string } | undefined>(
26
+ () => (renderMarkdownAsHTML ? { __html: renderMarkdownAsHTML(messageBackAction?.text || '') } : undefined),
27
+ [messageBackAction?.text, renderMarkdownAsHTML]
28
+ );
29
+ const disabled = uiState === 'disabled';
30
+ const title = messageBackAction && 'title' in messageBackAction && messageBackAction.title;
31
+
32
+ // Every starter prompt card action must have "title" field.
33
+ const shouldShowBlueprint = uiState === 'blueprint' || !title;
34
+
35
+ const handleClick = useCallback(() => {
36
+ setSendBoxValue(inputTextRef.current);
37
+
38
+ // Focus on the send box after the value is "pasted."
39
+ focus('sendBox');
40
+ }, [focus, inputTextRef, setSendBoxValue]);
41
+
42
+ return shouldShowBlueprint ? (
43
+ <div
44
+ className={cx(className, classNames['pre-chat-message-activity__card-action-box'])}
45
+ data-testid={testIds.preChatMessageActivityStarterPromptsCardAction}
46
+ />
47
+ ) : (
48
+ <button
49
+ aria-disabled={disabled ? true : undefined}
50
+ className={cx(className, classNames['pre-chat-message-activity__card-action-box'])}
51
+ data-testid={testIds.preChatMessageActivityStarterPromptsCardAction}
52
+ onClick={disabled ? undefined : handleClick}
53
+ // eslint-disable-next-line no-magic-numbers
54
+ tabIndex={disabled ? -1 : undefined}
55
+ type="button"
56
+ >
57
+ <div className={classNames['pre-chat-message-activity__card-action-title']}>{title}</div>
58
+ {'image' in messageBackAction && messageBackAction.image && (
59
+ <MonochromeImageMasker
60
+ className={classNames['pre-chat-message-activity__card-action-image']}
61
+ src={messageBackAction.image}
62
+ />
63
+ )}
64
+ <div
65
+ className={classNames['pre-chat-message-activity__card-action-subtitle']}
66
+ // eslint-disable-next-line react/no-danger
67
+ dangerouslySetInnerHTML={subtitleHTML}
68
+ />
69
+ </button>
70
+ );
71
+ };
72
+
73
+ StarterPromptsCardAction.displayName = 'StarterPromptsCardAction';
74
+
75
+ export default memo(StarterPromptsCardAction);
@@ -0,0 +1,18 @@
1
+ :global(.webchat-fluent) .pre-chat-message-activity__card-action-toolbar {
2
+ container-name: webchat-container;
3
+ container-type: inline-size;
4
+ }
5
+
6
+ :global(.webchat-fluent) .pre-chat-message-activity__card-action-toolbar-grid {
7
+ display: grid;
8
+ gap: var(--webchat-spacingHorizontalM);
9
+ grid-template-columns: 1fr 1fr 1fr;
10
+ padding: 0;
11
+ }
12
+
13
+ /* TODO: What is the good width to show as 3 columns? Web Chat, by default, has a bubble max width of 480px. */
14
+ @container webchat-container (width <= 480px) {
15
+ :global(.webchat-fluent) .pre-chat-message-activity__card-action-toolbar-grid {
16
+ grid-template-columns: 1fr;
17
+ }
18
+ }
@@ -0,0 +1,47 @@
1
+ import { hooks } from 'botframework-webchat-api';
2
+ import { type DirectLineCardAction } from 'botframework-webchat-core';
3
+ import cx from 'classnames';
4
+ import React, { Fragment, memo } from 'react';
5
+ import { useStyles } from '../../styles/index.js';
6
+ import StarterPromptsCardAction from './StarterPromptsCardAction.js';
7
+ import styles from './StarterPromptsToolbar.module.css';
8
+
9
+ const { useUIState } = hooks;
10
+
11
+ type Props = Readonly<{
12
+ cardActions: readonly DirectLineCardAction[];
13
+ className?: string | undefined;
14
+ }>;
15
+
16
+ const StarterPromptsToolbar = ({ cardActions, className }: Props) => {
17
+ const classNames = useStyles(styles);
18
+ const [uiState] = useUIState();
19
+
20
+ return (
21
+ // TODO: Accessibility-wise, this should be role="toolbar" with keyboard navigation.
22
+ <div className={cx(className, classNames['pre-chat-message-activity__card-action-toolbar'])}>
23
+ <div className={classNames['pre-chat-message-activity__card-action-toolbar-grid']}>
24
+ {uiState === 'blueprint' ? (
25
+ <Fragment>
26
+ <StarterPromptsCardAction />
27
+ <StarterPromptsCardAction />
28
+ <StarterPromptsCardAction />
29
+ </Fragment>
30
+ ) : (
31
+ cardActions
32
+ .filter<DirectLineCardAction & { type: 'messageBack' }>(
33
+ (card: DirectLineCardAction): card is DirectLineCardAction & { type: 'messageBack' } =>
34
+ card.type === 'messageBack'
35
+ )
36
+ // There is no other usable keys in card actions.
37
+ // eslint-disable-next-line react/no-array-index-key
38
+ .map((cardAction, index) => <StarterPromptsCardAction key={index} messageBackAction={cardAction} />)
39
+ )}
40
+ </div>
41
+ </div>
42
+ );
43
+ };
44
+
45
+ StarterPromptsToolbar.displayName = 'StarterPromptsToolbar';
46
+
47
+ export default memo(StarterPromptsToolbar);
@@ -0,0 +1,2 @@
1
+ export { default as isPreChatMessageActivity } from './isPreChatMessageActivity.js';
2
+ export { default as PreChatMessageActivity } from './PreChatMessageActivity.js';
@@ -0,0 +1,7 @@
1
+ import { getOrgSchemaMessage, type WebChatActivity } from 'botframework-webchat-core';
2
+
3
+ export default function isPreChatMessageActivity(
4
+ activity: undefined | WebChatActivity
5
+ ): activity is WebChatActivity & { type: 'message' } {
6
+ return !!(activity && getOrgSchemaMessage(activity?.entities || [])?.keywords?.includes('PreChatMessage'));
7
+ }
@@ -3,5 +3,5 @@
3
3
  border: 1px solid var(--webchat-colorNeutralStroke1);
4
4
  cursor: default;
5
5
  padding: 6px 8px;
6
- width: fit-content
6
+ width: fit-content;
7
7
  }
@@ -1,10 +1,10 @@
1
1
  import { hooks } from 'botframework-webchat-component';
2
- import React, { memo } from 'react';
3
2
  import cx from 'classnames';
4
- import styles from './Attachments.module.css';
3
+ import React, { memo } from 'react';
5
4
  import { useStyles } from '../../styles';
5
+ import styles from './Attachments.module.css';
6
6
 
7
- const { useLocalizer } = hooks;
7
+ const { useLocalizer, useUIState } = hooks;
8
8
 
9
9
  const attachmentsPluralStringIds = {
10
10
  one: 'TEXT_INPUT_ATTACHMENTS_ONE',
@@ -21,10 +21,11 @@ function Attachments({
21
21
  readonly attachments: readonly Readonly<{ blob: Blob | File; thumbnailURL?: URL | undefined }>[];
22
22
  readonly className?: string | undefined;
23
23
  }>) {
24
+ const [uiState] = useUIState();
24
25
  const classNames = useStyles(styles);
25
26
  const localizeWithPlural = useLocalizer({ plural: true });
26
27
 
27
- return attachments.length ? (
28
+ return uiState !== 'blueprint' && attachments.length ? (
28
29
  <div className={cx(classNames['sendbox__attachment'], className)}>
29
30
  {localizeWithPlural(attachmentsPluralStringIds, attachments.length)}
30
31
  </div>
@@ -1,15 +1,26 @@
1
+ import { useLiveRegion } from 'botframework-webchat-component/internal';
1
2
  import React, { memo } from 'react';
2
- import styles from './ErrorMessage.module.css';
3
3
  import { useStyles } from '../../styles';
4
+ import styles from './ErrorMessage.module.css';
5
+
6
+ type ErrorMessageProps = Readonly<{
7
+ error?: string | undefined;
8
+ id: string;
9
+ }>;
4
10
 
5
- function ErrorMessage(props: Readonly<{ id: string; error?: string | undefined }>) {
11
+ function ErrorMessage({ error, id }: ErrorMessageProps) {
6
12
  const classNames = useStyles(styles);
13
+
14
+ useLiveRegion(() => error && <div className="sendbox__error-message__status">{error}</div>, [error]);
15
+
7
16
  return (
8
17
  // eslint-disable-next-line react/forbid-dom-props
9
- <span className={classNames['sendbox__error-message']} id={props.id} role="alert">
10
- {props.error}
18
+ <span className={classNames['sendbox__error-message']} id={id}>
19
+ {error}
11
20
  </span>
12
21
  );
13
22
  }
14
23
 
24
+ ErrorMessage.displayName = 'ErrorMessage';
25
+
15
26
  export default memo(ErrorMessage);
@@ -1,13 +1,22 @@
1
1
  :global(.webchat-fluent) .sendbox {
2
+ --webchat__sendbox--padding: var(--webchat__padding--sendbox);
3
+
2
4
  color: var(--webchat-colorNeutralForeground1);
3
5
  font-family: var(--webchat-fontFamilyBase);
4
- padding: 0 10px 10px;
6
+ padding: var(--webchat__sendbox--padding);
5
7
  text-rendering: optimizeLegibility;
6
8
 
7
9
  --webchat-sendbox-attachment-area-active: ;
8
10
  --webchat-sendbox-border-radius: var(--webchat-borderRadiusLarge);
9
11
  }
10
12
 
13
+ /* Copilot variant */
14
+ :global(.webchat-fluent) .sendbox.variant-copilot {
15
+ .sendbox__text-counter:not(.sendbox__text-counter--error) {
16
+ visibility: hidden;
17
+ }
18
+ }
19
+
11
20
  :global(.webchat-fluent) .sendbox__sendbox {
12
21
  background-color: var(--webchat-colorNeutralBackground1);
13
22
  border-radius: var(--webchat-sendbox-border-radius);
@@ -19,14 +28,13 @@
19
28
  grid-template:
20
29
  [telephone-keypad-start] 'text-area' [telephone-keypad-end]
21
30
  var(--webchat-sendbox-attachment-area-active)
22
- 'controls' / [telephone-keypad] 1fr
23
- ;
31
+ 'controls' / [telephone-keypad] 1fr;
24
32
  line-height: 20px;
25
33
  padding: 8px;
26
34
  position: relative;
27
35
 
28
36
  &:has(.sendbox__attachment--in-grid) {
29
- --webchat-sendbox-attachment-area-active: 'attachment'
37
+ --webchat-sendbox-attachment-area-active: 'attachment';
30
38
  }
31
39
 
32
40
  &:focus-within {
@@ -39,7 +47,7 @@
39
47
  border-bottom: var(--webchat-strokeWidthThicker) solid var(--webchat-colorCompoundBrandForeground1Hover);
40
48
  bottom: -1px;
41
49
  clip-path: inset(calc(100% - var(--webchat-strokeWidthThicker)) 50% 0 50%);
42
- content: "";
50
+ content: '';
43
51
  height: var(--webchat-sendbox-border-radius);
44
52
  left: -1px;
45
53
  position: absolute;
@@ -77,8 +85,15 @@
77
85
  font-size: 14px;
78
86
  line-height: 20px;
79
87
  outline: none;
80
- padding: 4px 4px 0;
88
+ margin: var(--webchat-spacingVerticalXS) var(--webchat-spacingHorizontalXS) var(--webchat-spacingVerticalNone);
81
89
  resize: none;
90
+
91
+ /* Prevent zoom on focus on iOS */
92
+ @media only screen and (hover: none) and (pointer: coarse) {
93
+ &:focus-within {
94
+ font-size: 16px;
95
+ }
96
+ }
82
97
  }
83
98
 
84
99
  :global(.webchat-fluent) .sendbox__sendbox-controls {
@@ -1,8 +1,17 @@
1
- import { hooks, type SendBoxFocusOptions } from 'botframework-webchat-component';
1
+ import { hooks } from 'botframework-webchat-component';
2
2
  import cx from 'classnames';
3
- import React, { memo, useCallback, useRef, useState, type FormEventHandler, type MouseEventHandler } from 'react';
3
+ import React, {
4
+ memo,
5
+ ReactNode,
6
+ useCallback,
7
+ useRef,
8
+ useState,
9
+ type FormEventHandler,
10
+ type MouseEventHandler
11
+ } from 'react';
4
12
  import { useRefFrom } from 'use-ref-from';
5
13
  import { SendIcon } from '../../icons';
14
+ import { useStyles, useVariantClassName } from '../../styles';
6
15
  import testIds from '../../testIds';
7
16
  import { DropZone } from '../dropZone';
8
17
  import { SuggestedActions } from '../suggestedActions';
@@ -10,13 +19,13 @@ import { TelephoneKeypadSurrogate, useTelephoneKeypadShown, type DTMF } from '..
10
19
  import AddAttachmentButton from './AddAttachmentButton';
11
20
  import Attachments from './Attachments';
12
21
  import ErrorMessage from './ErrorMessage';
13
- import TelephoneKeypadToolbarButton from './TelephoneKeypadToolbarButton';
14
- import TextArea from './TextArea';
15
- import { Toolbar, ToolbarButton, ToolbarSeparator } from './Toolbar';
16
22
  import useSubmitError from './private/useSubmitError';
23
+ import useTranscriptNavigation from './private/useTranscriptNavigation';
17
24
  import useUniqueId from './private/useUniqueId';
18
25
  import styles from './SendBox.module.css';
19
- import { useStyles } from '../../styles';
26
+ import TelephoneKeypadToolbarButton from './TelephoneKeypadToolbarButton';
27
+ import TextArea from './TextArea';
28
+ import { Toolbar, ToolbarButton, ToolbarSeparator } from './Toolbar';
20
29
 
21
30
  const {
22
31
  useFocus,
@@ -24,57 +33,55 @@ const {
24
33
  useMakeThumbnail,
25
34
  useRegisterFocusSendBox,
26
35
  useSendBoxAttachments,
36
+ useSendBoxValue,
27
37
  useSendMessage,
28
- useStyleOptions
38
+ useStyleOptions,
39
+ useUIState
29
40
  } = hooks;
30
41
 
31
- function SendBox(
32
- props: Readonly<{
33
- className?: string | undefined;
34
- placeholder?: string | undefined;
35
- }>
36
- ) {
37
- const inputRef = useRef<HTMLTextAreaElement>(null);
38
- const [message, setMessage] = useState('');
39
- const [attachments, setAttachments] = useSendBoxAttachments();
42
+ type Props = Readonly<{
43
+ className?: string | undefined;
44
+ completion?: ReactNode | undefined;
45
+ isPrimary?: boolean | undefined;
46
+ placeholder?: string | undefined;
47
+ }>;
48
+
49
+ function SendBox(props: Props) {
40
50
  const [{ hideTelephoneKeypadButton, hideUploadButton, maxMessageLength }] = useStyleOptions();
41
- const isMessageLengthExceeded = !!maxMessageLength && message.length > maxMessageLength;
51
+ const [attachments, setAttachments] = useSendBoxAttachments();
52
+ const [globalMessage, setGlobalMessage] = useSendBoxValue();
53
+ const [localMessage, setLocalMessage] = useState('');
54
+ const [telephoneKeypadShown] = useTelephoneKeypadShown();
55
+ const [uiState] = useUIState();
42
56
  const classNames = useStyles(styles);
57
+ const variantClassName = useVariantClassName(styles);
58
+ const errorMessageId = useUniqueId('sendbox__error-message-id');
59
+ const inputRef = useRef<HTMLTextAreaElement>(null);
43
60
  const localize = useLocalizer();
44
- const sendMessage = useSendMessage();
45
61
  const makeThumbnail = useMakeThumbnail();
46
- const errorMessageId = useUniqueId('sendbox__error-message-id');
47
- const [errorRef, errorMessage] = useSubmitError({ message, attachments });
48
- const [telephoneKeypadShown] = useTelephoneKeypadShown();
62
+ const sendMessage = useSendMessage();
49
63
  const setFocus = useFocus();
50
64
 
65
+ const message = props.isPrimary ? globalMessage : localMessage;
66
+ const setMessage = props.isPrimary ? setGlobalMessage : setLocalMessage;
67
+ const isBlueprint = uiState === 'blueprint';
68
+
69
+ const [errorMessage, commitLatestError] = useSubmitError({ message, attachments });
70
+ const isMessageLengthExceeded = !!maxMessageLength && message.length > maxMessageLength;
71
+ const shouldShowMessageLength =
72
+ !isBlueprint && !telephoneKeypadShown && maxMessageLength && isFinite(maxMessageLength);
73
+ const shouldShowTelephoneKeypad = !isBlueprint && telephoneKeypadShown;
74
+
51
75
  useRegisterFocusSendBox(
52
76
  useCallback(
53
- ({ noKeyboard, waitUntil }: SendBoxFocusOptions) => {
54
- if (!inputRef.current) {
55
- return;
56
- }
57
- if (noKeyboard) {
58
- waitUntil(
59
- (async () => {
60
- const previousReadOnly = inputRef.current?.getAttribute('readonly');
61
- inputRef.current?.setAttribute('readonly', 'true');
62
- // TODO: [P2] We should update this logic to handle quickly-successive `focusCallback`.
63
- // If a succeeding `focusCallback` is being called, the `setTimeout` should run immediately.
64
- // Or the second `focusCallback` should not set `readonly` to `true`.
65
- // eslint-disable-next-line no-restricted-globals
66
- await new Promise(resolve => setTimeout(resolve, 0));
67
- inputRef.current?.focus();
68
- if (typeof previousReadOnly !== 'string') {
69
- inputRef.current?.removeAttribute('readonly');
70
- } else {
71
- inputRef.current?.setAttribute('readonly', previousReadOnly);
72
- }
73
- })()
74
- );
75
- } else {
76
- inputRef.current?.focus();
77
- }
77
+ ({ noKeyboard }) => {
78
+ const { current } = inputRef;
79
+
80
+ // Setting `inputMode` to `none` temporarily to suppress soft keyboard in iOS.
81
+ // We will revert the change once the end-user tap on the send box.
82
+ // This code path is only triggered when the user press "send" button to send the message, instead of pressing ENTER key.
83
+ noKeyboard && current?.setAttribute('inputmode', 'none');
84
+ current?.focus();
78
85
  },
79
86
  [inputRef]
80
87
  )
@@ -124,11 +131,14 @@ function SendBox(
124
131
  [makeThumbnail, setAttachments]
125
132
  );
126
133
 
134
+ const handleClick = useCallback(({ currentTarget }) => currentTarget.removeAttribute('inputmode'), []);
135
+
127
136
  const handleFormSubmit: FormEventHandler<HTMLFormElement> = useCallback(
128
137
  event => {
129
138
  event.preventDefault();
139
+ const error = commitLatestError();
130
140
 
131
- if (errorRef.current !== 'empty' && !isMessageLengthExceeded) {
141
+ if (error !== 'empty' && !isMessageLengthExceeded) {
132
142
  sendMessage(messageRef.current, undefined, { attachments: attachmentsRef.current });
133
143
 
134
144
  setMessage('');
@@ -137,32 +147,55 @@ function SendBox(
137
147
 
138
148
  setFocus('sendBox');
139
149
  },
140
- [attachmentsRef, messageRef, sendMessage, setAttachments, setMessage, isMessageLengthExceeded, errorRef, setFocus]
150
+ [
151
+ commitLatestError,
152
+ isMessageLengthExceeded,
153
+ setFocus,
154
+ sendMessage,
155
+ setMessage,
156
+ messageRef,
157
+ attachmentsRef,
158
+ setAttachments
159
+ ]
141
160
  );
142
161
 
143
162
  const handleTelephoneKeypadButtonClick = useCallback(
144
163
  // TODO: We need more official way of sending DTMF.
145
- (dtmf: DTMF) => sendMessage(`/DTMF ${dtmf}`),
164
+ (dtmf: DTMF) => sendMessage(`/DTMFKey ${dtmf}`),
146
165
  [sendMessage]
147
166
  );
148
167
 
168
+ const handleTranscriptNavigation = useTranscriptNavigation();
169
+
149
170
  const aria = {
150
171
  'aria-invalid': 'false' as const,
151
172
  ...(errorMessage && {
152
- 'aria-invalid': 'true' as const,
153
- 'aria-errormessage': errorMessageId
173
+ 'aria-describedby': errorMessageId,
174
+ 'aria-errormessage': errorMessageId,
175
+ 'aria-invalid': 'true' as const
154
176
  })
155
177
  };
156
178
 
157
179
  return (
158
- <form {...aria} className={cx(classNames['sendbox'], props.className)} onSubmit={handleFormSubmit}>
180
+ <form
181
+ {...aria}
182
+ className={cx(classNames['sendbox'], variantClassName, props.className)}
183
+ data-testid={testIds.sendBoxContainer}
184
+ onSubmit={handleFormSubmit}
185
+ >
159
186
  <SuggestedActions />
160
- <div className={cx(classNames['sendbox__sendbox'])} onClickCapture={handleSendBoxClick}>
187
+ <div
188
+ className={cx(classNames['sendbox__sendbox'])}
189
+ onClickCapture={handleSendBoxClick}
190
+ onKeyDown={handleTranscriptNavigation}
191
+ >
161
192
  <TextArea
162
193
  aria-label={isMessageLengthExceeded ? localize('TEXT_INPUT_LENGTH_EXCEEDED_ALT') : localize('TEXT_INPUT_ALT')}
163
194
  className={cx(classNames['sendbox__sendbox-text'], classNames['sendbox__text-area--in-grid'])}
195
+ completion={props.completion}
164
196
  data-testid={testIds.sendBoxTextBox}
165
- hidden={telephoneKeypadShown}
197
+ hidden={shouldShowTelephoneKeypad}
198
+ onClick={handleClick}
166
199
  onInput={handleMessageChange}
167
200
  placeholder={props.placeholder ?? localize('TEXT_INPUT_PLACEHOLDER')}
168
201
  ref={inputRef}
@@ -176,7 +209,7 @@ function SendBox(
176
209
  />
177
210
  <Attachments attachments={attachments} className={classNames['sendbox__attachment--in-grid']} />
178
211
  <div className={cx(classNames['sendbox__sendbox-controls'], classNames['sendbox__sendbox-controls--in-grid'])}>
179
- {!telephoneKeypadShown && maxMessageLength && (
212
+ {shouldShowMessageLength && (
180
213
  <div
181
214
  className={cx(classNames['sendbox__text-counter'], {
182
215
  [classNames['sendbox__text-counter--error']]: isMessageLengthExceeded
@@ -192,7 +225,7 @@ function SendBox(
192
225
  <ToolbarButton
193
226
  aria-label={localize('TEXT_INPUT_SEND_BUTTON_ALT')}
194
227
  data-testid={testIds.sendBoxSendButton}
195
- disabled={isMessageLengthExceeded || telephoneKeypadShown}
228
+ disabled={isMessageLengthExceeded || shouldShowTelephoneKeypad}
196
229
  type="submit"
197
230
  >
198
231
  <SendIcon />
@@ -206,4 +239,10 @@ function SendBox(
206
239
  );
207
240
  }
208
241
 
242
+ const PrimarySendBox = memo((props: Exclude<Props, 'primary'>) => <SendBox {...props} isPrimary={true} />);
243
+
244
+ PrimarySendBox.displayName = 'PrimarySendBox';
245
+
209
246
  export default memo(SendBox);
247
+
248
+ export { PrimarySendBox };
@@ -2,7 +2,8 @@
2
2
  display: grid;
3
3
  grid-template-areas: 'main';
4
4
  max-height: 200px;
5
- overflow: hidden;
5
+ overflow: auto;
6
+ scrollbar-gutter: stable;
6
7
  }
7
8
 
8
9
  :global(.webchat-fluent) .sendbox__text-area--hidden {
@@ -11,6 +12,22 @@
11
12
  visibility: collapse;
12
13
  }
13
14
 
15
+ :global(.webchat-fluent) .sendbox__text-area--in-completion {
16
+ .sendbox__text-area-doppelganger {
17
+ visibility: unset;
18
+ }
19
+
20
+ .sendbox__text-area-input {
21
+ background-color: transparent;
22
+ caret-color: var(--webchat-colorNeutralForeground1);
23
+ color: transparent;
24
+ }
25
+
26
+ textarea::placeholder {
27
+ color: transparent;
28
+ }
29
+ }
30
+
14
31
  :global(.webchat-fluent) .sendbox__text-area-shared {
15
32
  border: none;
16
33
  font: inherit;
@@ -18,11 +35,12 @@
18
35
  outline: inherit;
19
36
  overflow-wrap: anywhere;
20
37
  resize: inherit;
21
- scrollbar-gutter: stable;
22
38
  }
23
39
 
24
40
  :global(.webchat-fluent) .sendbox__text-area-doppelganger {
25
- overflow: hidden;
41
+ overflow: visible;
42
+ pointer-events: none;
43
+ user-select: none;
26
44
  visibility: hidden;
27
45
  white-space: pre-wrap;
28
46
  }
@@ -30,11 +48,12 @@
30
48
  :global(.webchat-fluent) .sendbox__text-area-input {
31
49
  background-color: inherit;
32
50
  color: currentColor;
33
- height: 100%;
51
+ contain: size;
52
+ overflow: hidden;
34
53
  padding: 0;
35
54
  }
36
55
 
37
- :global(.webchat-fluent) .sendbox__text-area-input--scroll {
56
+ :global(.webchat-fluent) .sendbox__text-area--scroll {
38
57
  /* Edge uses -webkit-scrollbar if scrollbar-* is not set */
39
58
  scrollbar-color: unset;
40
59
  scrollbar-width: unset;
@@ -44,17 +63,17 @@
44
63
 
45
64
  /* Chrome, Edge, and Safari */
46
65
  &::-webkit-scrollbar {
47
- width: 8px
66
+ width: 8px;
48
67
  }
49
68
 
50
69
  &::-webkit-scrollbar-track {
51
70
  background-color: var(--webchat-colorNeutralBackground5);
52
- border-radius: 16px
71
+ border-radius: 16px;
53
72
  }
54
73
 
55
74
  &::-webkit-scrollbar-thumb {
56
75
  background-color: var(--webchat-colorNeutralForeground2);
57
- border-radius: 16px
76
+ border-radius: 16px;
58
77
  }
59
78
 
60
79
  &::-webkit-scrollbar-corner {