botframework-webchat-fluent-theme 4.18.1-main.20250804.93043a9 → 4.18.1-main.20250916.f2f7323
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 -1
- package/dist/botframework-webchat-fluent-theme.d.mts +6 -5
- package/dist/botframework-webchat-fluent-theme.d.ts +6 -5
- package/dist/botframework-webchat-fluent-theme.development.css.map +1 -1
- package/dist/botframework-webchat-fluent-theme.development.js +8 -8
- package/dist/botframework-webchat-fluent-theme.development.js.map +1 -1
- package/dist/botframework-webchat-fluent-theme.js +1 -1
- package/dist/botframework-webchat-fluent-theme.js.map +1 -1
- package/dist/botframework-webchat-fluent-theme.mjs +1 -1
- package/dist/botframework-webchat-fluent-theme.mjs.map +1 -1
- package/dist/botframework-webchat-fluent-theme.production.min.css.map +1 -1
- package/dist/botframework-webchat-fluent-theme.production.min.js +8 -8
- package/dist/botframework-webchat-fluent-theme.production.min.js.map +1 -1
- package/package.json +8 -7
- package/src/buildInfo.ts +9 -0
- package/src/components/activity/ActivityDecorator.module.css +0 -237
- package/src/components/activity/ActivityDecorator.tsx +17 -12
- package/src/components/activity/ActivityLoader.module.css +1 -1
- package/src/components/activity/ActivityLoader.tsx +19 -6
- package/src/components/activity/CopilotMessageHeader.tsx +22 -7
- package/src/components/activity/PartGroupingDecorator.module.css +480 -0
- package/src/components/activity/PartGroupingDecorator.tsx +69 -0
- package/src/components/assets/AssetComposer.tsx +2 -0
- package/src/components/theme/Theme.module.css +85 -41
- package/src/env.d.ts +10 -0
- package/src/external.umd/botframework-webchat-api/decorator.ts +2 -0
- package/src/external.umd/botframework-webchat-api/index.ts +2 -0
- package/src/external.umd/botframework-webchat-component/decorator.ts +2 -0
- package/src/external.umd/botframework-webchat-component/internal.ts +2 -0
- package/src/external.umd/react.ts +2 -0
- package/src/index.ts +6 -11
- package/src/private/FluentThemeProvider.tsx +9 -1
- package/src/tsconfig.json +8 -12
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/components/sendBox/SendBox.tsx","../src/components/icon/FluentIcon.tsx","../../react-valibot/src/reactNode.ts","../../react-valibot/src/validateProps.ts","../../styles/src/react/private/useStyles.ts"],"names":["validateProps","propsSchema","props","isolationMode","useStyles","styles","useMemo","baseClassName","resultClassName"],"mappings":"AAAA,yNAA8B,8ECA8B,gGAC7C,4EASR,0CACoB,mECXS,kCCC2C,SCsCxEA,EAAAA,CACLC,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CAC4C,CAE1C,OAAOD,CAmBX,CChEA,SAESE,EAAAA,CAAsCC,CAAAA,CAAc,CAE3D,OAAOC,4BAAAA,CACL,CAAA,EACE,MAAA,CAAO,MAAA,CACL,MAAA,CAAO,WAAA,CACL,MAAA,CAAO,OAAA,CAAQD,CAAM,CAAA,CAAE,GAAA,CAAI,CAAC,CAACE,CAAAA,CAAeC,CAAe,CAAA,CAAA,EAAM,CAC/DD,CAAAA,CACA,CAAA,EAAA","file":"/__w/1/s/BotFramework-WebChat/packages/fluent-theme/dist/botframework-webchat-fluent-theme.js","sourcesContent":["import { injectMetaTag } from 'inject-meta-tag';\n\nimport { SendBox as FluentSendBox } from './components/sendBox/index';\nimport FluentThemeProvider from './private/FluentThemeProvider';\nimport testIds from './testIds';\n\nconst buildTool = process.env['build_tool'];\nconst moduleFormat = process.env['module_format'];\nconst version = process.env['npm_package_version'];\n\nconst buildInfo = { buildTool, moduleFormat, version };\n\ninjectMetaTag(\n 'botframework-webchat:fluent-theme',\n `version=${process.env['npm_package_version']}; build-tool=${process.env['build_tool']}; module-format=${process.env['module_format']}`\n);\n\nexport { FluentThemeProvider, FluentSendBox, buildInfo, testIds };\n","import { hooks, Components, type SendBoxFocusOptions } from 'botframework-webchat-component';\nimport cx from 'classnames';\nimport React, {\n memo,\n ReactNode,\n useCallback,\n useRef,\n useState,\n type FormEventHandler,\n type MouseEventHandler\n} from 'react';\nimport { useRefFrom } from 'use-ref-from';\n\nimport { FluentIcon } from '../icon';\nimport { useStyles, useVariantClassName } from '../../styles';\nimport testIds from '../../testIds';\nimport { DropZone } from '../dropZone';\nimport { SuggestedActions } from '../suggestedActions';\nimport { TelephoneKeypadSurrogate, useTelephoneKeypadShown, type DTMF } from '../telephoneKeypad';\nimport AddAttachmentButton from './AddAttachmentButton';\nimport ErrorMessage from './ErrorMessage';\nimport useSubmitError from './private/useSubmitError';\nimport useTranscriptNavigation from './private/useTranscriptNavigation';\nimport useUniqueId from './private/useUniqueId';\nimport styles from './SendBox.module.css';\nimport TelephoneKeypadToolbarButton from './TelephoneKeypadToolbarButton';\nimport { Toolbar, ToolbarButton, ToolbarSeparator } from './Toolbar';\n\nconst {\n useFocus,\n useLocalizer,\n useMakeThumbnail,\n useRegisterFocusSendBox,\n useSendBoxAttachments,\n useSendBoxValue,\n useSendMessage,\n useStyleOptions,\n useUIState\n} = hooks;\n\nconst { AttachmentBar, TextArea } = Components;\n\ntype Props = Readonly<{\n className?: string | undefined;\n completion?: ReactNode | undefined;\n isPrimary?: boolean | undefined;\n placeholder?: string | undefined;\n}>;\n\nfunction SendBox(props: Props) {\n const [{ disableFileUpload, hideTelephoneKeypadButton, maxMessageLength }] = useStyleOptions();\n const [attachments, setAttachments] = useSendBoxAttachments();\n const [globalMessage, setGlobalMessage] = useSendBoxValue();\n const [localMessage, setLocalMessage] = useState('');\n const [telephoneKeypadShown] = useTelephoneKeypadShown();\n const [uiState] = useUIState();\n const classNames = useStyles(styles);\n const variantClassName = useVariantClassName(styles);\n const errorMessageId = useUniqueId('sendbox__error-message-id');\n const inputRef = useRef<HTMLTextAreaElement>(null);\n const localize = useLocalizer();\n const makeThumbnail = useMakeThumbnail();\n const sendMessage = useSendMessage();\n const setFocus = useFocus();\n\n const message = props.isPrimary ? globalMessage : localMessage;\n const setMessage = props.isPrimary ? setGlobalMessage : setLocalMessage;\n const isBlueprint = uiState === 'blueprint';\n\n const [errorMessage, commitLatestError] = useSubmitError({ message, attachments });\n const isMessageLengthExceeded = !!maxMessageLength && message.length > maxMessageLength;\n const shouldShowMessageLength =\n !isBlueprint && !telephoneKeypadShown && maxMessageLength && isFinite(maxMessageLength);\n const shouldShowTelephoneKeypad = !isBlueprint && telephoneKeypadShown;\n\n useRegisterFocusSendBox(\n useCallback(\n ({ noKeyboard, waitUntil }: SendBoxFocusOptions) => {\n if (!inputRef.current) {\n return;\n }\n if (noKeyboard) {\n waitUntil(\n (async () => {\n const previousReadOnly = inputRef.current?.getAttribute('readonly');\n inputRef.current?.setAttribute('readonly', 'true');\n // TODO: [P2] We should update this logic to handle quickly-successive `focusCallback`.\n // If a succeeding `focusCallback` is being called, the `setTimeout` should run immediately.\n // Or the second `focusCallback` should not set `readonly` to `true`.\n // eslint-disable-next-line no-restricted-globals\n await new Promise(resolve => setTimeout(resolve, 0));\n inputRef.current?.focus();\n if (typeof previousReadOnly !== 'string') {\n inputRef.current?.removeAttribute('readonly');\n } else {\n inputRef.current?.setAttribute('readonly', previousReadOnly);\n }\n })()\n );\n } else {\n inputRef.current?.focus();\n }\n },\n [inputRef]\n )\n );\n\n const attachmentsRef = useRefFrom(attachments);\n const messageRef = useRefFrom(message);\n\n const handleSendBoxClick = useCallback<MouseEventHandler>(\n event => {\n if ('tabIndex' in event.target && typeof event.target.tabIndex === 'number' && event.target.tabIndex >= 0) {\n return;\n }\n\n setFocus('sendBox');\n },\n [setFocus]\n );\n\n const handleMessageChange: React.FormEventHandler<HTMLTextAreaElement> = useCallback(\n event => setMessage(event.currentTarget.value),\n [setMessage]\n );\n\n const handleAddFiles = useCallback(\n async (inputFiles: File[]) => {\n const newAttachments = Object.freeze(\n await Promise.all(\n inputFiles.map(file =>\n makeThumbnail(file).then(thumbnailURL =>\n Object.freeze({\n blob: file,\n ...(thumbnailURL && { thumbnailURL })\n })\n )\n )\n )\n );\n\n setAttachments(attachmentsRef.current.concat(newAttachments));\n },\n [attachmentsRef, makeThumbnail, setAttachments]\n );\n\n const handleFormSubmit: FormEventHandler<HTMLFormElement> = useCallback(\n event => {\n event.preventDefault();\n const error = commitLatestError();\n\n if (error !== 'empty' && !isMessageLengthExceeded) {\n sendMessage(messageRef.current, undefined, { attachments: attachmentsRef.current });\n\n setMessage('');\n setAttachments([]);\n }\n\n setFocus('sendBox');\n },\n [\n commitLatestError,\n isMessageLengthExceeded,\n setFocus,\n sendMessage,\n setMessage,\n messageRef,\n attachmentsRef,\n setAttachments\n ]\n );\n\n const handleTelephoneKeypadButtonClick = useCallback(\n // TODO: We need more official way of sending DTMF.\n (dtmf: DTMF) => sendMessage(`/DTMFKey ${dtmf}`),\n [sendMessage]\n );\n\n const handleTranscriptNavigation = useTranscriptNavigation();\n\n const aria = {\n 'aria-invalid': 'false' as const,\n ...(errorMessage && {\n 'aria-describedby': errorMessageId,\n 'aria-errormessage': errorMessageId,\n 'aria-invalid': 'true' as const\n })\n };\n\n return (\n <form\n {...aria}\n className={cx(classNames['sendbox'], variantClassName, props.className)}\n data-testid={testIds.sendBoxContainer}\n onSubmit={handleFormSubmit}\n >\n <SuggestedActions />\n <div\n className={cx(classNames['sendbox__sendbox'])}\n onClickCapture={handleSendBoxClick}\n onKeyDown={handleTranscriptNavigation}\n >\n <TextArea\n aria-label={isMessageLengthExceeded ? localize('TEXT_INPUT_LENGTH_EXCEEDED_ALT') : localize('TEXT_INPUT_ALT')}\n className={cx(classNames['sendbox__sendbox-text-area'], classNames['sendbox__text-area--in-grid'])}\n completion={props.completion}\n data-testid={testIds.sendBoxTextBox}\n hidden={shouldShowTelephoneKeypad}\n onInput={handleMessageChange}\n placeholder={props.placeholder ?? localize('TEXT_INPUT_PLACEHOLDER')}\n ref={inputRef}\n value={message}\n />\n <TelephoneKeypadSurrogate\n autoFocus={true}\n className={classNames['sendbox__telephone-keypad--in-grid']}\n isHorizontal={false}\n onButtonClick={handleTelephoneKeypadButtonClick}\n />\n {!isBlueprint && (\n <AttachmentBar\n className={cx(\n 'webchat__send-box__attachment-bar',\n classNames['sendbox__attachment-bar'],\n classNames['sendbox__attachment-bar--in-grid']\n )}\n />\n )}\n <div className={cx(classNames['sendbox__sendbox-controls'], classNames['sendbox__sendbox-controls--in-grid'])}>\n {shouldShowMessageLength && (\n <div\n className={cx(classNames['sendbox__text-counter'], {\n [classNames['sendbox__text-counter--error']]: isMessageLengthExceeded\n })}\n >\n {`${message.length}/${maxMessageLength}`}\n </div>\n )}\n <Toolbar>\n {!hideTelephoneKeypadButton && <TelephoneKeypadToolbarButton />}\n {!disableFileUpload && <AddAttachmentButton onFilesAdded={handleAddFiles} />}\n <ToolbarSeparator />\n <ToolbarButton\n aria-label={localize('TEXT_INPUT_SEND_BUTTON_ALT')}\n data-testid={testIds.sendBoxSendButton}\n disabled={isMessageLengthExceeded || shouldShowTelephoneKeypad}\n type=\"submit\"\n >\n <FluentIcon appearance=\"text\" icon=\"send\" />\n </ToolbarButton>\n </Toolbar>\n </div>\n {!disableFileUpload && <DropZone onFilesAdded={handleAddFiles} />}\n <ErrorMessage error={errorMessage} id={errorMessageId} />\n </div>\n </form>\n );\n}\n\nconst PrimarySendBox = memo((props: Exclude<Props, 'primary'>) => <SendBox {...props} isPrimary={true} />);\n\nPrimarySendBox.displayName = 'PrimarySendBox';\n\nexport default memo(SendBox);\n\nexport { PrimarySendBox };\n","import { createIconComponent } from 'botframework-webchat-component/internal';\nimport { validateProps } from '@msinternal/botframework-webchat-react-valibot';\nimport { useStyles } from '@msinternal/botframework-webchat-styles/react';\nimport cx from 'classnames';\nimport React, { memo, useMemo, type CSSProperties } from 'react';\nimport { object, optional, pipe, readonly, string, type InferInput } from 'valibot';\n\nimport styles from './FluentIcon.module.css';\n\nconst baseFluentIconPropsSchema = pipe(\n object({\n className: optional(string()),\n mask: optional(string())\n }),\n readonly()\n);\n\nfunction BaseFluentIcon(props: InferInput<typeof baseFluentIconPropsSchema>) {\n const { className } = validateProps(baseFluentIconPropsSchema, props);\n\n const classNames = useStyles(styles);\n\n const maskStyle = useMemo(\n () =>\n props.mask ? ({ '--webchat__fluent-icon--mask': `url(${JSON.stringify(props.mask)})` } as CSSProperties) : {},\n [props.mask]\n );\n\n return <div className={cx(classNames['fluent-icon'], className)} style={maskStyle} />;\n}\n\nconst { component: FluentIcon, modifierPropsSchema } = createIconComponent(\n styles,\n ['appearance', 'icon'],\n BaseFluentIcon\n);\n\nFluentIcon.displayName = 'FluentIcon';\n\nconst fluentIconPropsSchema = pipe(\n object({\n ...baseFluentIconPropsSchema.entries,\n ...modifierPropsSchema.entries\n }),\n readonly()\n);\n\ntype FluentIconProps = InferInput<typeof fluentIconPropsSchema>;\n\nexport default memo(FluentIcon);\nexport { fluentIconPropsSchema, type FluentIconProps };\n","import { type ReactNode } from 'react';\nimport { custom, type CustomIssue, type CustomSchema, type ErrorMessage } from 'valibot';\n\nfunction reactNode(): CustomSchema<ReactNode, undefined>;\n\nfunction reactNode<\n const TMessage extends ErrorMessage<CustomIssue> | undefined = ErrorMessage<CustomIssue> | undefined\n>(message: TMessage): CustomSchema<ReactNode, TMessage>;\n\nfunction reactNode<\n const TMessage extends ErrorMessage<CustomIssue> | undefined = ErrorMessage<CustomIssue> | undefined\n>(message?: TMessage): CustomSchema<ReactNode, TMessage> {\n return custom<ReactNode, TMessage>(\n () => true,\n // TODO: Probably lacking some undefined checks, thus, we need to force cast.\n message as TMessage\n );\n}\n\nexport default reactNode;\n","import { parse, safeParse, type BaseIssue, type BaseSchema, type InferInput, type InferOutput } from 'valibot';\n\n/**\n * Specifies the props isolation mode.\n *\n * - `\"no isolation\"` will return the props as-is without cloning or modifications\n * - `\"strict\"` will isolate the props using `valibot.parse()`\n * - Depends on schema design, it could clone object instances, remove extraneous properties from objects, or fill in optional fields\n */\ntype IsolationMode = 'no isolation' | 'strict';\n\ndeclare const process: {\n env: {\n NODE_ENV?: string | undefined;\n };\n};\n\nexport default function validateProps<const TSchema extends BaseSchema<unknown, unknown, BaseIssue<unknown>>>(\n propsSchema: TSchema,\n props: unknown,\n isolationMode?: 'no isolation' | undefined\n): InferInput<TSchema>;\n\nexport default function validateProps<const TSchema extends BaseSchema<unknown, unknown, BaseIssue<unknown>>>(\n propsSchema: TSchema,\n props: unknown,\n isolationMode?: 'strict'\n): InferOutput<TSchema>;\n\n/**\n * Validates props against the specified valibot schema when running under development mode.\n *\n * This function will not perform any validations when running under production mode.\n *\n * @param propsSchema validation schema\n * @param props props to validate\n * @param mode specifies the isolation mode, default to `\"no isolation\"`\n * @returns\n */\nexport default function validateProps<const TSchema extends BaseSchema<unknown, unknown, BaseIssue<unknown>>>(\n propsSchema: TSchema,\n props: InferInput<TSchema>,\n isolationMode?: IsolationMode | undefined\n): InferInput<TSchema> | InferOutput<TSchema> {\n if (process.env.NODE_ENV === 'production') {\n return props as unknown as InferInput<TSchema>;\n }\n\n if (isolationMode !== 'strict' && safeParse(propsSchema, props).success) {\n return props as unknown as InferInput<TSchema>;\n }\n\n // Code path hit here when under strict isolation, or no isolation and failed earlier.\n\n try {\n return parse(propsSchema, props);\n } catch (error) {\n console.error(\n 'botframework-webchat: Validation error while parsing props.',\n error && typeof error === 'object' && 'issues' in error && error.issues\n );\n\n throw error;\n }\n}\n","import { useMemo } from 'react';\n\nfunction useStyles<T extends CSSModuleClasses>(styles: T): T {\n // @ts-expect-error: entries/fromEntries don't allow to specify keys type\n return useMemo(\n () =>\n Object.freeze(\n Object.fromEntries(\n Object.entries(styles).map(([baseClassName, resultClassName]) => [\n baseClassName,\n `${baseClassName} ${resultClassName}`\n ])\n )\n ),\n [styles]\n );\n}\n\nexport default useStyles;\n"]}
|
|
1
|
+
{"version":3,"sources":["/__w/1/s/BotFramework-WebChat/packages/fluent-theme/dist.tmp/botframework-webchat-fluent-theme.js","../src/components/sendBox/SendBox.tsx","../src/components/icon/FluentIcon.tsx","../../react-valibot/src/reactNode.ts","../../react-valibot/src/validateProps.ts","../../styles/src/react/private/useStyles.ts"],"names":["reactNode","message","custom","reactNode_default","validateProps","propsSchema","props","isolationMode","useStyles","styles","useMemo","baseClassName","resultClassName"],"mappings":"AAAA,6KAAI,EAAE,CAAC,CAAC,EAAE,CAAC,MAAM,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,mDAAmD,CAAC,CAAC,EAAE,WAAW,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,CAAC,CCAnY,8EAA4D,gGAC7C,4EASR,0CACoB,mECXS,kCCC2C,SAQtEA,EAAAA,CAEPC,CAAAA,CAAuD,CACvD,OAAOC,6BAAAA,CACL,CAAA,EAAM,CAAA,CAAA,CAEND,CACF,CACF,CAEA,IAAOE,EAAAA,CAAQH,EAAAA,CCoBA,SAARI,CAAAA,CACLC,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CAC4C,CAE5C,EAAA,CAAI,CAAA,CAAA,OAAOD,CAAAA,CAAU,GAAA,CAAA,CAMnB,OAAOA,CAoBX,CCvEA,SAESE,EAAAA,CAAsCC,CAAAA,CAAc,CAE3D,OAAOC,4BAAAA,CACL,CAAA,EACE,MAAA,CAAO,MAAA,CACL,MAAA,CAAO,WAAA,CACL,MAAA,CAAO,OAAA,CAAQD,CAAM,CAAA,CAAE,GAAA,CAAI,CAAC,CAACE,CAAAA,CAAeC,CAAe,CAAA,CAAA,EAAM,CAC/DD,CAAAA,CACA,CAAA,EAAA","file":"/__w/1/s/BotFramework-WebChat/packages/fluent-theme/dist.tmp/botframework-webchat-fluent-theme.js","sourcesContent":[null,"import { hooks, Components, type SendBoxFocusOptions } from 'botframework-webchat-component';\nimport cx from 'classnames';\nimport React, {\n memo,\n ReactNode,\n useCallback,\n useRef,\n useState,\n type FormEventHandler,\n type MouseEventHandler\n} from 'react';\nimport { useRefFrom } from 'use-ref-from';\n\nimport { FluentIcon } from '../icon';\nimport { useStyles, useVariantClassName } from '../../styles';\nimport testIds from '../../testIds';\nimport { DropZone } from '../dropZone';\nimport { SuggestedActions } from '../suggestedActions';\nimport { TelephoneKeypadSurrogate, useTelephoneKeypadShown, type DTMF } from '../telephoneKeypad';\nimport AddAttachmentButton from './AddAttachmentButton';\nimport ErrorMessage from './ErrorMessage';\nimport useSubmitError from './private/useSubmitError';\nimport useTranscriptNavigation from './private/useTranscriptNavigation';\nimport useUniqueId from './private/useUniqueId';\nimport styles from './SendBox.module.css';\nimport TelephoneKeypadToolbarButton from './TelephoneKeypadToolbarButton';\nimport { Toolbar, ToolbarButton, ToolbarSeparator } from './Toolbar';\n\nconst {\n useFocus,\n useLocalizer,\n useMakeThumbnail,\n useRegisterFocusSendBox,\n useSendBoxAttachments,\n useSendBoxValue,\n useSendMessage,\n useStyleOptions,\n useUIState\n} = hooks;\n\nconst { AttachmentBar, TextArea } = Components;\n\ntype Props = Readonly<{\n className?: string | undefined;\n completion?: ReactNode | undefined;\n isPrimary?: boolean | undefined;\n placeholder?: string | undefined;\n}>;\n\nfunction SendBox(props: Props) {\n const [{ disableFileUpload, hideTelephoneKeypadButton, maxMessageLength }] = useStyleOptions();\n const [attachments, setAttachments] = useSendBoxAttachments();\n const [globalMessage, setGlobalMessage] = useSendBoxValue();\n const [localMessage, setLocalMessage] = useState('');\n const [telephoneKeypadShown] = useTelephoneKeypadShown();\n const [uiState] = useUIState();\n const classNames = useStyles(styles);\n const variantClassName = useVariantClassName(styles);\n const errorMessageId = useUniqueId('sendbox__error-message-id');\n const inputRef = useRef<HTMLTextAreaElement>(null);\n const localize = useLocalizer();\n const makeThumbnail = useMakeThumbnail();\n const sendMessage = useSendMessage();\n const setFocus = useFocus();\n\n const message = props.isPrimary ? globalMessage : localMessage;\n const setMessage = props.isPrimary ? setGlobalMessage : setLocalMessage;\n const isBlueprint = uiState === 'blueprint';\n\n const [errorMessage, commitLatestError] = useSubmitError({ message, attachments });\n const isMessageLengthExceeded = !!maxMessageLength && message.length > maxMessageLength;\n const shouldShowMessageLength =\n !isBlueprint && !telephoneKeypadShown && maxMessageLength && isFinite(maxMessageLength);\n const shouldShowTelephoneKeypad = !isBlueprint && telephoneKeypadShown;\n\n useRegisterFocusSendBox(\n useCallback(\n ({ noKeyboard, waitUntil }: SendBoxFocusOptions) => {\n if (!inputRef.current) {\n return;\n }\n if (noKeyboard) {\n waitUntil(\n (async () => {\n const previousReadOnly = inputRef.current?.getAttribute('readonly');\n inputRef.current?.setAttribute('readonly', 'true');\n // TODO: [P2] We should update this logic to handle quickly-successive `focusCallback`.\n // If a succeeding `focusCallback` is being called, the `setTimeout` should run immediately.\n // Or the second `focusCallback` should not set `readonly` to `true`.\n // eslint-disable-next-line no-restricted-globals\n await new Promise(resolve => setTimeout(resolve, 0));\n inputRef.current?.focus();\n if (typeof previousReadOnly !== 'string') {\n inputRef.current?.removeAttribute('readonly');\n } else {\n inputRef.current?.setAttribute('readonly', previousReadOnly);\n }\n })()\n );\n } else {\n inputRef.current?.focus();\n }\n },\n [inputRef]\n )\n );\n\n const attachmentsRef = useRefFrom(attachments);\n const messageRef = useRefFrom(message);\n\n const handleSendBoxClick = useCallback<MouseEventHandler>(\n event => {\n if ('tabIndex' in event.target && typeof event.target.tabIndex === 'number' && event.target.tabIndex >= 0) {\n return;\n }\n\n setFocus('sendBox');\n },\n [setFocus]\n );\n\n const handleMessageChange: React.FormEventHandler<HTMLTextAreaElement> = useCallback(\n event => setMessage(event.currentTarget.value),\n [setMessage]\n );\n\n const handleAddFiles = useCallback(\n async (inputFiles: File[]) => {\n const newAttachments = Object.freeze(\n await Promise.all(\n inputFiles.map(file =>\n makeThumbnail(file).then(thumbnailURL =>\n Object.freeze({\n blob: file,\n ...(thumbnailURL && { thumbnailURL })\n })\n )\n )\n )\n );\n\n setAttachments(attachmentsRef.current.concat(newAttachments));\n },\n [attachmentsRef, makeThumbnail, setAttachments]\n );\n\n const handleFormSubmit: FormEventHandler<HTMLFormElement> = useCallback(\n event => {\n event.preventDefault();\n const error = commitLatestError();\n\n if (error !== 'empty' && !isMessageLengthExceeded) {\n sendMessage(messageRef.current, undefined, { attachments: attachmentsRef.current });\n\n setMessage('');\n setAttachments([]);\n }\n\n setFocus('sendBox');\n },\n [\n commitLatestError,\n isMessageLengthExceeded,\n setFocus,\n sendMessage,\n setMessage,\n messageRef,\n attachmentsRef,\n setAttachments\n ]\n );\n\n const handleTelephoneKeypadButtonClick = useCallback(\n // TODO: We need more official way of sending DTMF.\n (dtmf: DTMF) => sendMessage(`/DTMFKey ${dtmf}`),\n [sendMessage]\n );\n\n const handleTranscriptNavigation = useTranscriptNavigation();\n\n const aria = {\n 'aria-invalid': 'false' as const,\n ...(errorMessage && {\n 'aria-describedby': errorMessageId,\n 'aria-errormessage': errorMessageId,\n 'aria-invalid': 'true' as const\n })\n };\n\n return (\n <form\n {...aria}\n className={cx(classNames['sendbox'], variantClassName, props.className)}\n data-testid={testIds.sendBoxContainer}\n onSubmit={handleFormSubmit}\n >\n <SuggestedActions />\n <div\n className={cx(classNames['sendbox__sendbox'])}\n onClickCapture={handleSendBoxClick}\n onKeyDown={handleTranscriptNavigation}\n >\n <TextArea\n aria-label={isMessageLengthExceeded ? localize('TEXT_INPUT_LENGTH_EXCEEDED_ALT') : localize('TEXT_INPUT_ALT')}\n className={cx(classNames['sendbox__sendbox-text-area'], classNames['sendbox__text-area--in-grid'])}\n completion={props.completion}\n data-testid={testIds.sendBoxTextBox}\n hidden={shouldShowTelephoneKeypad}\n onInput={handleMessageChange}\n placeholder={props.placeholder ?? localize('TEXT_INPUT_PLACEHOLDER')}\n ref={inputRef}\n value={message}\n />\n <TelephoneKeypadSurrogate\n autoFocus={true}\n className={classNames['sendbox__telephone-keypad--in-grid']}\n isHorizontal={false}\n onButtonClick={handleTelephoneKeypadButtonClick}\n />\n {!isBlueprint && (\n <AttachmentBar\n className={cx(\n 'webchat__send-box__attachment-bar',\n classNames['sendbox__attachment-bar'],\n classNames['sendbox__attachment-bar--in-grid']\n )}\n />\n )}\n <div className={cx(classNames['sendbox__sendbox-controls'], classNames['sendbox__sendbox-controls--in-grid'])}>\n {shouldShowMessageLength && (\n <div\n className={cx(classNames['sendbox__text-counter'], {\n [classNames['sendbox__text-counter--error']]: isMessageLengthExceeded\n })}\n >\n {`${message.length}/${maxMessageLength}`}\n </div>\n )}\n <Toolbar>\n {!hideTelephoneKeypadButton && <TelephoneKeypadToolbarButton />}\n {!disableFileUpload && <AddAttachmentButton onFilesAdded={handleAddFiles} />}\n <ToolbarSeparator />\n <ToolbarButton\n aria-label={localize('TEXT_INPUT_SEND_BUTTON_ALT')}\n data-testid={testIds.sendBoxSendButton}\n disabled={isMessageLengthExceeded || shouldShowTelephoneKeypad}\n type=\"submit\"\n >\n <FluentIcon appearance=\"text\" icon=\"send\" />\n </ToolbarButton>\n </Toolbar>\n </div>\n {!disableFileUpload && <DropZone onFilesAdded={handleAddFiles} />}\n <ErrorMessage error={errorMessage} id={errorMessageId} />\n </div>\n </form>\n );\n}\n\nconst PrimarySendBox = memo((props: Exclude<Props, 'primary'>) => <SendBox {...props} isPrimary={true} />);\n\nPrimarySendBox.displayName = 'PrimarySendBox';\n\nexport default memo(SendBox);\n\nexport { PrimarySendBox };\n","import { createIconComponent } from 'botframework-webchat-component/internal';\nimport { validateProps } from '@msinternal/botframework-webchat-react-valibot';\nimport { useStyles } from '@msinternal/botframework-webchat-styles/react';\nimport cx from 'classnames';\nimport React, { memo, useMemo, type CSSProperties } from 'react';\nimport { object, optional, pipe, readonly, string, type InferInput } from 'valibot';\n\nimport styles from './FluentIcon.module.css';\n\nconst baseFluentIconPropsSchema = pipe(\n object({\n className: optional(string()),\n mask: optional(string())\n }),\n readonly()\n);\n\nfunction BaseFluentIcon(props: InferInput<typeof baseFluentIconPropsSchema>) {\n const { className } = validateProps(baseFluentIconPropsSchema, props);\n\n const classNames = useStyles(styles);\n\n const maskStyle = useMemo(\n () =>\n props.mask ? ({ '--webchat__fluent-icon--mask': `url(${JSON.stringify(props.mask)})` } as CSSProperties) : {},\n [props.mask]\n );\n\n return <div className={cx(classNames['fluent-icon'], className)} style={maskStyle} />;\n}\n\nconst { component: FluentIcon, modifierPropsSchema } = createIconComponent(\n styles,\n ['appearance', 'icon'],\n BaseFluentIcon\n);\n\nFluentIcon.displayName = 'FluentIcon';\n\nconst fluentIconPropsSchema = pipe(\n object({\n ...baseFluentIconPropsSchema.entries,\n ...modifierPropsSchema.entries\n }),\n readonly()\n);\n\ntype FluentIconProps = InferInput<typeof fluentIconPropsSchema>;\n\nexport default memo(FluentIcon);\nexport { fluentIconPropsSchema, type FluentIconProps };\n","import { type ReactNode } from 'react';\nimport { custom, type CustomIssue, type CustomSchema, type ErrorMessage } from 'valibot';\n\nfunction reactNode(): CustomSchema<ReactNode, undefined>;\n\nfunction reactNode<\n const TMessage extends ErrorMessage<CustomIssue> | undefined = ErrorMessage<CustomIssue> | undefined\n>(message: TMessage): CustomSchema<ReactNode, TMessage>;\n\nfunction reactNode<\n const TMessage extends ErrorMessage<CustomIssue> | undefined = ErrorMessage<CustomIssue> | undefined\n>(message?: TMessage): CustomSchema<ReactNode, TMessage> {\n return custom<ReactNode, TMessage>(\n () => true,\n // TODO: Probably lacking some undefined checks, thus, we need to force cast.\n message as TMessage\n );\n}\n\nexport default reactNode;\n","import { parse, safeParse, type BaseIssue, type BaseSchema, type InferInput, type InferOutput } from 'valibot';\n\n/**\n * Specifies the props isolation mode.\n *\n * - `\"no isolation\"` will return the props as-is without cloning or modifications\n * - `\"strict\"` will isolate the props using `valibot.parse()`\n * - Depends on schema design, it could clone object instances, remove extraneous properties from objects, or fill in optional fields\n */\ntype IsolationMode = 'no isolation' | 'strict';\n\ndeclare const process: {\n env: {\n NODE_ENV?: string | undefined;\n };\n};\n\nexport default function validateProps<const TSchema extends BaseSchema<unknown, unknown, BaseIssue<unknown>>>(\n propsSchema: TSchema,\n props: unknown,\n isolationMode?: 'no isolation' | undefined\n): InferInput<TSchema>;\n\nexport default function validateProps<const TSchema extends BaseSchema<unknown, unknown, BaseIssue<unknown>>>(\n propsSchema: TSchema,\n props: unknown,\n isolationMode?: 'strict'\n): InferOutput<TSchema>;\n\n/**\n * Validates props against the specified valibot schema when running under development mode.\n *\n * This function will not perform any validations when running under production mode.\n *\n * @param propsSchema validation schema\n * @param props props to validate\n * @param mode specifies the isolation mode, default to `\"no isolation\"`\n * @returns\n */\nexport default function validateProps<const TSchema extends BaseSchema<unknown, unknown, BaseIssue<unknown>>>(\n propsSchema: TSchema,\n props: InferInput<TSchema>,\n isolationMode?: IsolationMode | undefined\n): InferInput<TSchema> | InferOutput<TSchema> {\n // When an error boundary caught an error, React will render some components again with `props` of `undefined`.\n if (typeof props === 'undefined') {\n // This is probably React rendering while an error is caught, assume it is okay.\n return;\n }\n\n if (process.env.NODE_ENV === 'production') {\n return props as unknown as InferInput<TSchema>;\n }\n\n if (isolationMode !== 'strict' && safeParse(propsSchema, props).success) {\n return props as unknown as InferInput<TSchema>;\n }\n\n // Code path hit here when under strict isolation, or no isolation and failed earlier.\n\n try {\n return parse(propsSchema, props);\n } catch (error) {\n const validationError = new Error('botframework-webchat: Validation error while parsing props.');\n\n console.error(validationError, error && typeof error === 'object' && 'issues' in error && error.issues);\n\n validationError.cause = error;\n\n throw validationError;\n }\n}\n","import { useMemo } from 'react';\n\nfunction useStyles<T extends CSSModuleClasses>(styles: T): T {\n // @ts-expect-error: entries/fromEntries don't allow to specify keys type\n return useMemo(\n () =>\n Object.freeze(\n Object.fromEntries(\n Object.entries(styles).map(([baseClassName, resultClassName]) => [\n baseClassName,\n `${baseClassName} ${resultClassName}`\n ])\n )\n ),\n [styles]\n );\n}\n\nexport default useStyles;\n"]}
|