botframework-webchat-fluent-theme 4.17.0-main.20240425.edca85d → 4.17.0-main.20240502.a8ff179
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/botframework-webchat-fluent-theme.css.map +1 -0
- package/dist/{index.d.mts → botframework-webchat-fluent-theme.d.mts} +7 -1
- package/dist/{index.d.ts → botframework-webchat-fluent-theme.d.ts} +7 -1
- package/dist/botframework-webchat-fluent-theme.development.css.map +1 -1
- package/dist/botframework-webchat-fluent-theme.development.d.ts +2 -0
- package/dist/botframework-webchat-fluent-theme.development.js +2 -2424
- package/dist/botframework-webchat-fluent-theme.development.js.map +1 -1
- package/dist/botframework-webchat-fluent-theme.js +3 -0
- package/dist/botframework-webchat-fluent-theme.js.map +1 -0
- package/dist/botframework-webchat-fluent-theme.mjs +3 -0
- package/dist/botframework-webchat-fluent-theme.mjs.map +1 -0
- package/dist/botframework-webchat-fluent-theme.production.min.css.map +1 -1
- package/dist/botframework-webchat-fluent-theme.production.min.d.ts +2 -0
- package/dist/botframework-webchat-fluent-theme.production.min.js +2 -10
- package/dist/botframework-webchat-fluent-theme.production.min.js.map +1 -1
- package/package.json +17 -12
- package/src/components/dropZone/{index.tsx → DropZone.tsx} +2 -2
- package/src/components/dropZone/index.ts +1 -0
- package/src/components/{sendbox → sendBox}/AddAttachmentButton.tsx +1 -1
- package/src/components/{sendbox/index.tsx → sendBox/SendBox.tsx} +52 -13
- package/src/components/{sendbox → sendBox}/TelephoneKeypadToolbarButton.tsx +2 -2
- package/src/components/sendBox/index.tsx +1 -0
- package/src/components/suggestedActions/SuggestedAction.tsx +4 -2
- package/src/components/suggestedActions/{index.tsx → SuggestedActions.tsx} +19 -9
- package/src/components/suggestedActions/index.ts +1 -0
- package/src/components/suggestedActions/private/rovingFocus.tsx +180 -0
- package/src/components/telephoneKeypad/Surrogate.tsx +2 -2
- package/src/components/telephoneKeypad/private/TelephoneKeypad.tsx +4 -7
- package/src/components/{Theme.tsx → theme/Theme.tsx} +1 -1
- package/src/components/theme/index.ts +1 -0
- package/src/icons/AddDocumentIcon.tsx +4 -2
- package/src/icons/AttachmentIcon.tsx +4 -2
- package/src/icons/InfoSmallIcon.tsx +19 -15
- package/src/icons/SendIcon.tsx +4 -2
- package/src/icons/{TelephoneKeypad.tsx → TelephoneKeypadIcon.tsx} +4 -2
- package/src/icons/index.ts +5 -0
- package/src/index.ts +11 -4
- package/src/private/FluentThemeProvider.tsx +3 -3
- package/src/styles/index.ts +4 -0
- package/src/types/index.ts +1 -0
- package/dist/index.css.map +0 -1
- package/dist/index.js +0 -1151
- package/dist/index.js.map +0 -1
- package/dist/index.mjs +0 -1115
- package/dist/index.mjs.map +0 -1
- package/src/components/DropZone.tsx +0 -3
- package/src/components/SendBox.tsx +0 -3
- package/src/components/SuggestedActions.tsx +0 -3
- package/src/components/TelephoneKeypad.tsx +0 -1
- package/src/styles.ts +0 -4
- /package/src/components/dropZone/{index.module.css → DropZone.module.css} +0 -0
- /package/src/components/{sendbox → sendBox}/AddAttachmentButton.module.css +0 -0
- /package/src/components/{sendbox → sendBox}/Attachments.module.css +0 -0
- /package/src/components/{sendbox → sendBox}/Attachments.tsx +0 -0
- /package/src/components/{sendbox → sendBox}/ErrorMessage.module.css +0 -0
- /package/src/components/{sendbox → sendBox}/ErrorMessage.tsx +0 -0
- /package/src/components/{sendbox/index.module.css → sendBox/SendBox.module.css} +0 -0
- /package/src/components/{sendbox → sendBox}/TextArea.module.css +0 -0
- /package/src/components/{sendbox → sendBox}/TextArea.tsx +0 -0
- /package/src/components/{sendbox → sendBox}/Toolbar.module.css +0 -0
- /package/src/components/{sendbox → sendBox}/Toolbar.tsx +0 -0
- /package/src/components/{sendbox → sendBox}/private/useSubmitError.ts +0 -0
- /package/src/components/{sendbox → sendBox}/private/useUniqueId.ts +0 -0
- /package/src/components/suggestedActions/{index.module.css → SuggestedActions.module.css} +0 -0
- /package/src/components/{Theme.module.css → theme/Theme.module.css} +0 -0
package/package.json
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "botframework-webchat-fluent-theme",
|
|
3
|
-
"version": "4.17.0-main.
|
|
3
|
+
"version": "4.17.0-main.20240502.a8ff179",
|
|
4
4
|
"description": "Fluent theme for Bot Framework Web Chat",
|
|
5
|
-
"main": "./dist/
|
|
6
|
-
"types": "./dist/
|
|
5
|
+
"main": "./dist/botframework-webchat-fluent-theme.js",
|
|
6
|
+
"types": "./dist/botframework-webchat-fluent-theme.d.ts",
|
|
7
7
|
"publishConfig": {
|
|
8
8
|
"access": "public"
|
|
9
9
|
},
|
|
10
10
|
"exports": {
|
|
11
11
|
".": {
|
|
12
12
|
"import": {
|
|
13
|
-
"
|
|
14
|
-
"
|
|
13
|
+
"types": "./dist/botframework-webchat-fluent-theme.d.mts",
|
|
14
|
+
"default": "./dist/botframework-webchat-fluent-theme.mjs"
|
|
15
15
|
},
|
|
16
16
|
"required": {
|
|
17
|
-
"
|
|
18
|
-
"
|
|
17
|
+
"types": "./dist/botframework-webchat-fluent-theme.d.ts",
|
|
18
|
+
"default": "./dist/botframework-webchat-fluent-theme.js"
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
},
|
|
@@ -54,19 +54,24 @@
|
|
|
54
54
|
"precommit:typecheck": "tsc --project ./src --emitDeclarationOnly false --esModuleInterop true --noEmit --pretty false",
|
|
55
55
|
"start": "npm run build -- --watch"
|
|
56
56
|
},
|
|
57
|
-
"pinDependencies": {
|
|
57
|
+
"pinDependencies": {
|
|
58
|
+
"@types/react": [
|
|
59
|
+
"16",
|
|
60
|
+
"react@16.8.6 is our baseline"
|
|
61
|
+
]
|
|
62
|
+
},
|
|
58
63
|
"devDependencies": {
|
|
59
64
|
"@tsconfig/strictest": "^2.0.5",
|
|
60
65
|
"@types/math-random": "^1.0.2",
|
|
61
66
|
"@types/node": "^20.10.3",
|
|
62
|
-
"@types/react": "^
|
|
67
|
+
"@types/react": "^16.14.60",
|
|
63
68
|
"tsup": "^8.0.2",
|
|
64
69
|
"typescript": "^5.3.2"
|
|
65
70
|
},
|
|
66
71
|
"dependencies": {
|
|
67
|
-
"botframework-webchat-api": "4.17.0-main.
|
|
68
|
-
"botframework-webchat-component": "4.17.0-main.
|
|
69
|
-
"botframework-webchat-core": "4.17.0-main.
|
|
72
|
+
"botframework-webchat-api": "4.17.0-main.20240502.a8ff179",
|
|
73
|
+
"botframework-webchat-component": "4.17.0-main.20240502.a8ff179",
|
|
74
|
+
"botframework-webchat-core": "4.17.0-main.20240502.a8ff179",
|
|
70
75
|
"classnames": "^2.5.1",
|
|
71
76
|
"inject-meta-tag": "^0.0.1",
|
|
72
77
|
"math-random": "^2.0.1",
|
|
@@ -3,9 +3,9 @@ import cx from 'classnames';
|
|
|
3
3
|
import React, { memo, useCallback, useEffect, useRef, useState, type DragEventHandler } from 'react';
|
|
4
4
|
import { useRefFrom } from 'use-ref-from';
|
|
5
5
|
|
|
6
|
-
import { AddDocumentIcon } from '../../icons
|
|
6
|
+
import { AddDocumentIcon } from '../../icons';
|
|
7
7
|
import testIds from '../../testIds';
|
|
8
|
-
import styles from './
|
|
8
|
+
import styles from './DropZone.module.css';
|
|
9
9
|
import { useStyles } from '../../styles';
|
|
10
10
|
|
|
11
11
|
const { useLocalizer } = hooks;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as DropZone } from './DropZone';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { hooks } from 'botframework-webchat-component';
|
|
2
2
|
import React, { useCallback, useRef, type ChangeEventHandler, memo } from 'react';
|
|
3
3
|
import { useRefFrom } from 'use-ref-from';
|
|
4
|
-
import { AttachmentIcon } from '../../icons
|
|
4
|
+
import { AttachmentIcon } from '../../icons';
|
|
5
5
|
import testIds from '../../testIds';
|
|
6
6
|
import { ToolbarButton } from './Toolbar';
|
|
7
7
|
import styles from './AddAttachmentButton.module.css';
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { hooks } from 'botframework-webchat-component';
|
|
1
|
+
import { hooks, type SendBoxFocusOptions } from 'botframework-webchat-component';
|
|
2
2
|
import cx from 'classnames';
|
|
3
3
|
import React, { memo, useCallback, useRef, useState, type FormEventHandler, type MouseEventHandler } from 'react';
|
|
4
4
|
import { useRefFrom } from 'use-ref-from';
|
|
5
|
-
import { SendIcon } from '../../icons
|
|
5
|
+
import { SendIcon } from '../../icons';
|
|
6
6
|
import testIds from '../../testIds';
|
|
7
|
-
import DropZone from '../
|
|
8
|
-
import SuggestedActions from '../
|
|
9
|
-
import { TelephoneKeypadSurrogate, useTelephoneKeypadShown, type DTMF } from '../
|
|
7
|
+
import { DropZone } from '../dropZone';
|
|
8
|
+
import { SuggestedActions } from '../suggestedActions';
|
|
9
|
+
import { TelephoneKeypadSurrogate, useTelephoneKeypadShown, type DTMF } from '../telephoneKeypad';
|
|
10
10
|
import AddAttachmentButton from './AddAttachmentButton';
|
|
11
11
|
import Attachments from './Attachments';
|
|
12
12
|
import ErrorMessage from './ErrorMessage';
|
|
@@ -15,10 +15,18 @@ import TextArea from './TextArea';
|
|
|
15
15
|
import { Toolbar, ToolbarButton, ToolbarSeparator } from './Toolbar';
|
|
16
16
|
import useSubmitError from './private/useSubmitError';
|
|
17
17
|
import useUniqueId from './private/useUniqueId';
|
|
18
|
-
import styles from './
|
|
18
|
+
import styles from './SendBox.module.css';
|
|
19
19
|
import { useStyles } from '../../styles';
|
|
20
20
|
|
|
21
|
-
const {
|
|
21
|
+
const {
|
|
22
|
+
useFocus,
|
|
23
|
+
useLocalizer,
|
|
24
|
+
useMakeThumbnail,
|
|
25
|
+
useRegisterFocusSendBox,
|
|
26
|
+
useSendBoxAttachments,
|
|
27
|
+
useSendMessage,
|
|
28
|
+
useStyleOptions
|
|
29
|
+
} = hooks;
|
|
22
30
|
|
|
23
31
|
function SendBox(
|
|
24
32
|
props: Readonly<{
|
|
@@ -38,6 +46,39 @@ function SendBox(
|
|
|
38
46
|
const errorMessageId = useUniqueId('sendbox__error-message-id');
|
|
39
47
|
const [errorRef, errorMessage] = useSubmitError({ message, attachments });
|
|
40
48
|
const [telephoneKeypadShown] = useTelephoneKeypadShown();
|
|
49
|
+
const setFocus = useFocus();
|
|
50
|
+
|
|
51
|
+
useRegisterFocusSendBox(
|
|
52
|
+
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
|
+
}
|
|
78
|
+
},
|
|
79
|
+
[inputRef]
|
|
80
|
+
)
|
|
81
|
+
);
|
|
41
82
|
|
|
42
83
|
const attachmentsRef = useRefFrom(attachments);
|
|
43
84
|
const messageRef = useRefFrom(message);
|
|
@@ -48,10 +89,9 @@ function SendBox(
|
|
|
48
89
|
return;
|
|
49
90
|
}
|
|
50
91
|
|
|
51
|
-
|
|
52
|
-
inputRef.current?.focus();
|
|
92
|
+
setFocus('sendBox');
|
|
53
93
|
},
|
|
54
|
-
[
|
|
94
|
+
[setFocus]
|
|
55
95
|
);
|
|
56
96
|
|
|
57
97
|
const handleMessageChange: React.FormEventHandler<HTMLTextAreaElement> = useCallback(
|
|
@@ -95,10 +135,9 @@ function SendBox(
|
|
|
95
135
|
setAttachments([]);
|
|
96
136
|
}
|
|
97
137
|
|
|
98
|
-
|
|
99
|
-
inputRef.current?.focus();
|
|
138
|
+
setFocus('sendBox');
|
|
100
139
|
},
|
|
101
|
-
[attachmentsRef, messageRef, sendMessage, setAttachments, setMessage, isMessageLengthExceeded, errorRef,
|
|
140
|
+
[attachmentsRef, messageRef, sendMessage, setAttachments, setMessage, isMessageLengthExceeded, errorRef, setFocus]
|
|
102
141
|
);
|
|
103
142
|
|
|
104
143
|
const handleTelephoneKeypadButtonClick = useCallback(
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React, { memo, useCallback } from 'react';
|
|
2
2
|
|
|
3
3
|
import { hooks } from 'botframework-webchat-component';
|
|
4
|
-
import { TelephoneKeypadIcon } from '../../icons
|
|
4
|
+
import { TelephoneKeypadIcon } from '../../icons';
|
|
5
5
|
import testIds from '../../testIds';
|
|
6
|
-
import { useTelephoneKeypadShown } from '../
|
|
6
|
+
import { useTelephoneKeypadShown } from '../telephoneKeypad';
|
|
7
7
|
import { ToolbarButton } from './Toolbar';
|
|
8
8
|
|
|
9
9
|
const { useLocalizer } = hooks;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as SendBox } from './SendBox';
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { hooks } from 'botframework-webchat-component';
|
|
2
2
|
import { type DirectLineCardAction } from 'botframework-webchat-core';
|
|
3
3
|
import cx from 'classnames';
|
|
4
|
-
import React, { MouseEventHandler, memo, useCallback
|
|
4
|
+
import React, { MouseEventHandler, memo, useCallback } from 'react';
|
|
5
5
|
import styles from './SuggestedAction.module.css';
|
|
6
6
|
import { useStyles } from '../../styles';
|
|
7
7
|
import AccessibleButton from './AccessibleButton';
|
|
8
|
+
import { useRovingFocusItemRef } from './private/rovingFocus';
|
|
8
9
|
|
|
9
10
|
const { useDisabled, useFocus, usePerformCardAction, useScrollToEnd, useStyleSet, useSuggestedActions } = hooks;
|
|
10
11
|
|
|
@@ -36,6 +37,7 @@ function SuggestedAction({
|
|
|
36
37
|
displayText,
|
|
37
38
|
image,
|
|
38
39
|
imageAlt,
|
|
40
|
+
itemIndex,
|
|
39
41
|
text,
|
|
40
42
|
type,
|
|
41
43
|
value
|
|
@@ -44,7 +46,7 @@ function SuggestedAction({
|
|
|
44
46
|
const [{ suggestedAction: suggestedActionStyleSet }] = useStyleSet();
|
|
45
47
|
const [disabled] = useDisabled();
|
|
46
48
|
const focus = useFocus();
|
|
47
|
-
const focusRef =
|
|
49
|
+
const focusRef = useRovingFocusItemRef<HTMLButtonElement>(itemIndex);
|
|
48
50
|
const performCardAction = usePerformCardAction();
|
|
49
51
|
const classNames = useStyles(styles);
|
|
50
52
|
const scrollToEnd = useScrollToEnd();
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { hooks } from 'botframework-webchat-component';
|
|
2
2
|
import cx from 'classnames';
|
|
3
|
-
import React, { memo, type ReactNode } from 'react';
|
|
3
|
+
import React, { memo, useCallback, type ReactNode } from 'react';
|
|
4
4
|
import SuggestedAction from './SuggestedAction';
|
|
5
5
|
import computeSuggestedActionText from './private/computeSuggestedActionText';
|
|
6
|
-
import styles from './
|
|
6
|
+
import styles from './SuggestedActions.module.css';
|
|
7
7
|
import { useStyles } from '../../styles';
|
|
8
|
+
import RovingFocusProvider from './private/rovingFocus';
|
|
8
9
|
|
|
9
|
-
const { useLocalizer, useStyleOptions, useStyleSet, useSuggestedActions } = hooks;
|
|
10
|
+
const { useFocus, useLocalizer, useStyleOptions, useStyleSet, useSuggestedActions } = hooks;
|
|
10
11
|
|
|
11
12
|
function SuggestedActionStackedOrFlowContainer(
|
|
12
13
|
props: Readonly<{
|
|
@@ -44,6 +45,12 @@ function SuggestedActions() {
|
|
|
44
45
|
const classNames = useStyles(styles);
|
|
45
46
|
const localize = useLocalizer();
|
|
46
47
|
const [suggestedActions] = useSuggestedActions();
|
|
48
|
+
const focus = useFocus();
|
|
49
|
+
|
|
50
|
+
const handleEscapeKey = useCallback(() => {
|
|
51
|
+
focus('sendBox');
|
|
52
|
+
}, [focus]);
|
|
53
|
+
|
|
47
54
|
const children = suggestedActions.map((cardAction, index) => {
|
|
48
55
|
const { displayText, image, imageAltText, text, type, value } = cardAction as {
|
|
49
56
|
displayText?: string;
|
|
@@ -85,13 +92,16 @@ function SuggestedActions() {
|
|
|
85
92
|
/>
|
|
86
93
|
);
|
|
87
94
|
});
|
|
95
|
+
|
|
88
96
|
return (
|
|
89
|
-
<
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
97
|
+
<RovingFocusProvider onEscapeKey={handleEscapeKey}>
|
|
98
|
+
<SuggestedActionStackedOrFlowContainer
|
|
99
|
+
aria-label={localize('SUGGESTED_ACTIONS_LABEL_ALT')}
|
|
100
|
+
className={classNames['suggested-actions']}
|
|
101
|
+
>
|
|
102
|
+
{children}
|
|
103
|
+
</SuggestedActionStackedOrFlowContainer>
|
|
104
|
+
</RovingFocusProvider>
|
|
95
105
|
);
|
|
96
106
|
}
|
|
97
107
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as SuggestedActions } from './SuggestedActions';
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/* eslint-disable no-magic-numbers */
|
|
2
|
+
import React, {
|
|
3
|
+
type MutableRefObject,
|
|
4
|
+
createContext,
|
|
5
|
+
memo,
|
|
6
|
+
useCallback,
|
|
7
|
+
useContext,
|
|
8
|
+
useEffect,
|
|
9
|
+
useMemo,
|
|
10
|
+
useRef
|
|
11
|
+
} from 'react';
|
|
12
|
+
|
|
13
|
+
type ItemRef = MutableRefObject<HTMLElement | undefined>;
|
|
14
|
+
|
|
15
|
+
type RovingFocusContextType = {
|
|
16
|
+
itemEffector: <T extends HTMLElement>(ref: MutableRefObject<T | null>, index: number) => () => void;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const RovingFocusContext = createContext<RovingFocusContextType>({
|
|
20
|
+
itemEffector: () => {
|
|
21
|
+
// This will be implemented when using in <RovingFocusProvider>.
|
|
22
|
+
throw new Error('botframework-webchat-fluent-theme rovingFocus: no provider for RovingFocusContext.');
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
function RovingFocusProvider(
|
|
27
|
+
props: Readonly<{
|
|
28
|
+
children?: React.ReactNode | undefined;
|
|
29
|
+
direction?: 'vertical' | 'horizontal' | undefined;
|
|
30
|
+
onEscapeKey?: () => void;
|
|
31
|
+
}>
|
|
32
|
+
) {
|
|
33
|
+
const activeItemIndexRef = useRef(0);
|
|
34
|
+
const itemRefsRef = useRef<ItemRef[]>([]);
|
|
35
|
+
|
|
36
|
+
const updateItemTabIndex = useCallback(
|
|
37
|
+
({ current }: ItemRef, index: number) =>
|
|
38
|
+
current && (current.tabIndex = activeItemIndexRef.current === index ? 0 : -1),
|
|
39
|
+
[activeItemIndexRef]
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const setActiveItemIndex = useCallback(
|
|
43
|
+
(valueOrFunction: number | ((value: number) => number)) => {
|
|
44
|
+
// All calls to this function is expected to be under event handlers (post-rendering).
|
|
45
|
+
let nextActiveItemIndex;
|
|
46
|
+
|
|
47
|
+
if (typeof valueOrFunction === 'number') {
|
|
48
|
+
nextActiveItemIndex = valueOrFunction;
|
|
49
|
+
} else {
|
|
50
|
+
nextActiveItemIndex = valueOrFunction(activeItemIndexRef.current);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// If the index points to no item, fallback to the first item.
|
|
54
|
+
// This makes sure at least one of the item in the container is selected.
|
|
55
|
+
if (nextActiveItemIndex && !itemRefsRef.current.at(nextActiveItemIndex)?.current) {
|
|
56
|
+
nextActiveItemIndex = 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (activeItemIndexRef.current !== nextActiveItemIndex) {
|
|
60
|
+
activeItemIndexRef.current = nextActiveItemIndex;
|
|
61
|
+
|
|
62
|
+
itemRefsRef.current.forEach((ref, index) => updateItemTabIndex(ref, index));
|
|
63
|
+
itemRefsRef.current.at(nextActiveItemIndex)?.current?.focus();
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
[updateItemTabIndex, itemRefsRef, activeItemIndexRef]
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const handleFocus = useCallback(
|
|
70
|
+
event => {
|
|
71
|
+
const { target } = event;
|
|
72
|
+
|
|
73
|
+
const index = itemRefsRef.current.findIndex(({ current }) => current === target);
|
|
74
|
+
|
|
75
|
+
// prevent focusing the last element, if we didn't found the element focused
|
|
76
|
+
if (index !== -1) {
|
|
77
|
+
setActiveItemIndex(index);
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
[itemRefsRef, setActiveItemIndex]
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const handleSetNextActive = useCallback(
|
|
84
|
+
(key: string) =>
|
|
85
|
+
(currentIndex: number): number => {
|
|
86
|
+
const isUnidirectional = !props.direction;
|
|
87
|
+
const isVerticalMove = /up|down/iu.test(key) && props.direction === 'vertical';
|
|
88
|
+
const isHorizontalMove = /left|right/iu.test(key) && props.direction === 'horizontal';
|
|
89
|
+
const isForwardMove = /right|down/iu.test(key);
|
|
90
|
+
const direction = isUnidirectional || isVerticalMove || isHorizontalMove ? (isForwardMove ? 1 : -1) : 0;
|
|
91
|
+
// The `itemRefsRef` array could be a sparse array.
|
|
92
|
+
// Thus, the next item may not be immediately next to the current one.
|
|
93
|
+
const itemIndices = itemRefsRef.current.map((_, index) => index);
|
|
94
|
+
const nextIndex = itemIndices.indexOf(currentIndex) + direction;
|
|
95
|
+
|
|
96
|
+
return itemIndices.at(nextIndex) ?? 0;
|
|
97
|
+
},
|
|
98
|
+
[props.direction]
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const handleKeyDown = useCallback<(event: KeyboardEvent) => void>(
|
|
102
|
+
event => {
|
|
103
|
+
const { key } = event;
|
|
104
|
+
|
|
105
|
+
switch (key) {
|
|
106
|
+
case 'Up':
|
|
107
|
+
case 'ArrowUp':
|
|
108
|
+
case 'Left':
|
|
109
|
+
case 'ArrowLeft':
|
|
110
|
+
case 'Down':
|
|
111
|
+
case 'ArrowDown':
|
|
112
|
+
case 'Right':
|
|
113
|
+
case 'ArrowRight':
|
|
114
|
+
setActiveItemIndex(handleSetNextActive(key));
|
|
115
|
+
break;
|
|
116
|
+
|
|
117
|
+
case 'Home':
|
|
118
|
+
setActiveItemIndex(0);
|
|
119
|
+
break;
|
|
120
|
+
|
|
121
|
+
case 'End':
|
|
122
|
+
setActiveItemIndex(-1);
|
|
123
|
+
break;
|
|
124
|
+
|
|
125
|
+
case 'Escape':
|
|
126
|
+
props.onEscapeKey?.();
|
|
127
|
+
break;
|
|
128
|
+
|
|
129
|
+
default:
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
event.preventDefault();
|
|
134
|
+
event.stopPropagation();
|
|
135
|
+
},
|
|
136
|
+
[setActiveItemIndex, handleSetNextActive, props]
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const itemEffector = useCallback(
|
|
140
|
+
(ref, index) => {
|
|
141
|
+
const { current } = ref;
|
|
142
|
+
|
|
143
|
+
itemRefsRef.current[Number(index)] = ref;
|
|
144
|
+
|
|
145
|
+
current.addEventListener('focus', handleFocus);
|
|
146
|
+
current.addEventListener('keydown', handleKeyDown);
|
|
147
|
+
|
|
148
|
+
updateItemTabIndex(ref, index);
|
|
149
|
+
|
|
150
|
+
return () => {
|
|
151
|
+
current.removeEventListener('focus', handleFocus);
|
|
152
|
+
current.removeEventListener('keydown', handleKeyDown);
|
|
153
|
+
|
|
154
|
+
delete itemRefsRef.current[Number(index)];
|
|
155
|
+
};
|
|
156
|
+
},
|
|
157
|
+
[handleFocus, handleKeyDown, updateItemTabIndex, itemRefsRef]
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
const value = useMemo<RovingFocusContextType>(
|
|
161
|
+
() => ({
|
|
162
|
+
itemEffector
|
|
163
|
+
}),
|
|
164
|
+
[itemEffector]
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
return <RovingFocusContext.Provider value={value}>{props.children}</RovingFocusContext.Provider>;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function useRovingFocusItemRef<T extends HTMLElement>(itemIndex: number): MutableRefObject<T | null> {
|
|
171
|
+
const ref = useRef<T>(null);
|
|
172
|
+
|
|
173
|
+
const { itemEffector } = useContext(RovingFocusContext);
|
|
174
|
+
|
|
175
|
+
useEffect(() => itemEffector(ref, itemIndex));
|
|
176
|
+
|
|
177
|
+
return ref;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export default memo(RovingFocusProvider);
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import React, { memo } from 'react';
|
|
2
2
|
|
|
3
|
-
import type { PropsOf } from '../../types
|
|
3
|
+
import type { PropsOf } from '../../types';
|
|
4
4
|
import TelephoneKeypad from './private/TelephoneKeypad';
|
|
5
5
|
import useShown from './useShown';
|
|
6
6
|
|
|
7
7
|
type Props = PropsOf<typeof TelephoneKeypad>;
|
|
8
8
|
|
|
9
|
-
const TelephoneKeypadSurrogate = memo((props: Props) => (useShown()[0] ? <TelephoneKeypad {...props} /> :
|
|
9
|
+
const TelephoneKeypadSurrogate = memo((props: Props) => (useShown()[0] ? <TelephoneKeypad {...props} /> : null));
|
|
10
10
|
|
|
11
11
|
TelephoneKeypadSurrogate.displayName = 'TelephoneKeypad.Surrogate';
|
|
12
12
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Components } from 'botframework-webchat-component';
|
|
2
2
|
import cx from 'classnames';
|
|
3
|
+
import React, { memo, useCallback, useEffect, useRef, type KeyboardEventHandler, type ReactNode } from 'react';
|
|
3
4
|
import { useRefFrom } from 'use-ref-from';
|
|
4
|
-
import { Components } from 'botframework-webchat-component';
|
|
5
5
|
|
|
6
6
|
import Button from './Button';
|
|
7
7
|
// import HorizontalDialPadController from './HorizontalDialPadController';
|
|
@@ -10,7 +10,7 @@ import { type DTMF } from '../types';
|
|
|
10
10
|
import useShown from '../useShown';
|
|
11
11
|
import styles from './TelephoneKeypad.module.css';
|
|
12
12
|
import { useStyles } from '../../../styles';
|
|
13
|
-
import InfoSmallIcon from '../../../icons
|
|
13
|
+
import { InfoSmallIcon } from '../../../icons';
|
|
14
14
|
|
|
15
15
|
const { LocalizedString } = Components;
|
|
16
16
|
|
|
@@ -25,10 +25,7 @@ const Orientation = memo(
|
|
|
25
25
|
({ children, isHorizontal }: Readonly<{ children?: ReactNode | undefined; isHorizontal: boolean }>) => {
|
|
26
26
|
const classNames = useStyles(styles);
|
|
27
27
|
|
|
28
|
-
return isHorizontal ? (
|
|
29
|
-
// <HorizontalDialPadController>{children}</HorizontalDialPadController>
|
|
30
|
-
false
|
|
31
|
-
) : (
|
|
28
|
+
return isHorizontal ? null : ( // <HorizontalDialPadController>{children}</HorizontalDialPadController>
|
|
32
29
|
<div className={classNames['telephone-keypad__box']}>{children}</div>
|
|
33
30
|
);
|
|
34
31
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as WebChatTheme } from './Theme';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { memo } from 'react';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
function AddDocumentIcon(props: Readonly<{ readonly className?: string }>) {
|
|
4
4
|
return (
|
|
5
5
|
<svg
|
|
6
6
|
aria-hidden="true"
|
|
@@ -17,3 +17,5 @@ export function AddDocumentIcon(props: Readonly<{ readonly className?: string }>
|
|
|
17
17
|
</svg>
|
|
18
18
|
);
|
|
19
19
|
}
|
|
20
|
+
|
|
21
|
+
export default memo(AddDocumentIcon);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { memo } from 'react';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
function AttachmentIcon(props: Readonly<{ readonly className?: string }>) {
|
|
4
4
|
return (
|
|
5
5
|
<svg
|
|
6
6
|
aria-hidden="true"
|
|
@@ -17,3 +17,5 @@ export function AttachmentIcon(props: Readonly<{ readonly className?: string }>)
|
|
|
17
17
|
</svg>
|
|
18
18
|
);
|
|
19
19
|
}
|
|
20
|
+
|
|
21
|
+
export default memo(AttachmentIcon);
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import React, { memo } from 'react';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
3
|
+
function InfoSmallIcon(props: Readonly<{ readonly className?: string }>) {
|
|
4
|
+
return (
|
|
5
|
+
<svg
|
|
6
|
+
aria-hidden="true"
|
|
7
|
+
className={props.className}
|
|
8
|
+
height="1em"
|
|
9
|
+
viewBox="0 0 16 16"
|
|
10
|
+
width="1em"
|
|
11
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
12
|
+
>
|
|
13
|
+
<path
|
|
14
|
+
d="M8.5 7.5a.5.5 0 1 0-1 0v3a.5.5 0 0 0 1 0v-3Zm.25-2a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1ZM2 8a6 6 0 1 1 12 0A6 6 0 0 1 2 8Z"
|
|
15
|
+
fill="currentColor"
|
|
16
|
+
/>
|
|
17
|
+
</svg>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default memo(InfoSmallIcon);
|
package/src/icons/SendIcon.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { memo } from 'react';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
function SendIcon(props: Readonly<{ readonly className?: string }>) {
|
|
4
4
|
return (
|
|
5
5
|
<svg
|
|
6
6
|
aria-hidden="true"
|
|
@@ -17,3 +17,5 @@ export function SendIcon(props: Readonly<{ readonly className?: string }>) {
|
|
|
17
17
|
</svg>
|
|
18
18
|
);
|
|
19
19
|
}
|
|
20
|
+
|
|
21
|
+
export default memo(SendIcon);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { memo } from 'react';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
function TelephoneKeypadIcon(props: Readonly<{ readonly className?: string }>) {
|
|
4
4
|
return (
|
|
5
5
|
<svg
|
|
6
6
|
aria-hidden="true"
|
|
@@ -17,3 +17,5 @@ export function TelephoneKeypadIcon(props: Readonly<{ readonly className?: strin
|
|
|
17
17
|
</svg>
|
|
18
18
|
);
|
|
19
19
|
}
|
|
20
|
+
|
|
21
|
+
export default memo(TelephoneKeypadIcon);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { default as AddDocumentIcon } from './AddDocumentIcon';
|
|
2
|
+
export { default as AttachmentIcon } from './AttachmentIcon';
|
|
3
|
+
export { default as InfoSmallIcon } from './InfoSmallIcon';
|
|
4
|
+
export { default as SendIcon } from './SendIcon';
|
|
5
|
+
export { default as TelephoneKeypadIcon } from './TelephoneKeypadIcon';
|
package/src/index.ts
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import { injectMetaTag } from 'inject-meta-tag';
|
|
2
2
|
|
|
3
3
|
import FluentThemeProvider from './private/FluentThemeProvider';
|
|
4
|
-
import testIds from './testIds';
|
|
5
4
|
import { injectStyle } from './styles';
|
|
5
|
+
import testIds from './testIds';
|
|
6
|
+
|
|
7
|
+
const buildTool = process.env['build_tool'];
|
|
8
|
+
const moduleFormat = process.env['module_format'];
|
|
9
|
+
const version = process.env['npm_package_version'];
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
const buildInfo = { buildTool, moduleFormat, version };
|
|
8
12
|
|
|
9
|
-
injectMetaTag(
|
|
13
|
+
injectMetaTag(
|
|
14
|
+
'botframework-webchat:fluent-theme',
|
|
15
|
+
`version=${process.env['npm_package_version']}; build-tool=${process.env['build_tool']}; module-format=${process.env['module_format']}`
|
|
16
|
+
);
|
|
10
17
|
|
|
11
18
|
injectStyle();
|
|
12
19
|
|
|
13
|
-
export { FluentThemeProvider, testIds };
|
|
20
|
+
export { FluentThemeProvider, buildInfo, testIds };
|