botframework-webchat-component
Version:
React component of botframework-webchat
1,026 lines (855 loc) • 162 kB
JavaScript
"use strict";
function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _botframeworkWebchatApi = require("botframework-webchat-api");
var _reactScrollToBottom = require("react-scroll-to-bottom");
var _classnames = _interopRequireDefault(require("classnames"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _mathRandom = _interopRequireDefault(require("math-random"));
var _react = _interopRequireWildcard(require("react"));
var _BasicTypingIndicator = _interopRequireDefault(require("./BasicTypingIndicator"));
var _Fade = _interopRequireDefault(require("./Utils/Fade"));
var _FocusRedirector = _interopRequireDefault(require("./Utils/FocusRedirector"));
var _getActivityUniqueId = _interopRequireDefault(require("./Utils/getActivityUniqueId"));
var _getTabIndex = _interopRequireDefault(require("./Utils/TypeFocusSink/getTabIndex"));
var _inputtableKey = _interopRequireDefault(require("./Utils/TypeFocusSink/inputtableKey"));
var _intersectionOf = _interopRequireDefault(require("./Utils/intersectionOf"));
var _isZeroOrPositive = _interopRequireDefault(require("./Utils/isZeroOrPositive"));
var _removeInline = _interopRequireDefault(require("./Utils/removeInline"));
var _ScreenReaderActivity = _interopRequireDefault(require("./ScreenReaderActivity"));
var _ScreenReaderText = _interopRequireDefault(require("./ScreenReaderText"));
var _ScrollToEndButton = _interopRequireDefault(require("./Activity/ScrollToEndButton"));
var _Speak = _interopRequireDefault(require("./Activity/Speak"));
var _tabbableElements = _interopRequireDefault(require("./Utils/tabbableElements"));
var _useAcknowledgedActivity = _interopRequireDefault(require("./hooks/internal/useAcknowledgedActivity"));
var _useDispatchScrollPosition = _interopRequireDefault(require("./hooks/internal/useDispatchScrollPosition"));
var _useDispatchTranscriptFocus = _interopRequireDefault(require("./hooks/internal/useDispatchTranscriptFocus"));
var _useFocus = _interopRequireDefault(require("./hooks/useFocus"));
var _useMemoize = _interopRequireDefault(require("./hooks/internal/useMemoize"));
var _useRegisterFocusTranscript = _interopRequireDefault(require("./hooks/internal/useRegisterFocusTranscript"));
var _useRegisterScrollRelative = _interopRequireDefault(require("./hooks/internal/useRegisterScrollRelative"));
var _useRegisterScrollTo = _interopRequireDefault(require("./hooks/internal/useRegisterScrollTo"));
var _useRegisterScrollToEnd = _interopRequireDefault(require("./hooks/internal/useRegisterScrollToEnd"));
var _useStyleSet5 = _interopRequireDefault(require("./hooks/useStyleSet"));
var _useStyleToEmotionObject = _interopRequireDefault(require("./hooks/internal/useStyleToEmotionObject"));
var _useUniqueId = _interopRequireDefault(require("./hooks/internal/useUniqueId"));
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; }
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter); }
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
function _iterableToArrayLimit(arr, i) { if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return; var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
var useActivities = _botframeworkWebchatApi.hooks.useActivities,
useCreateActivityRenderer = _botframeworkWebchatApi.hooks.useCreateActivityRenderer,
useCreateActivityStatusRenderer = _botframeworkWebchatApi.hooks.useCreateActivityStatusRenderer,
useCreateAvatarRenderer = _botframeworkWebchatApi.hooks.useCreateAvatarRenderer,
useDirection = _botframeworkWebchatApi.hooks.useDirection,
useGroupActivities = _botframeworkWebchatApi.hooks.useGroupActivities,
useLocalizer = _botframeworkWebchatApi.hooks.useLocalizer,
useStyleOptions = _botframeworkWebchatApi.hooks.useStyleOptions;
var ROOT_STYLE = {
'&.webchat__basic-transcript': {
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
// Make sure to set "position: relative" here to form another stacking context for the scroll-to-end button.
// Stacking context help isolating elements that use "z-index" from global pollution.
// https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context
position: 'relative',
'& .webchat__basic-transcript__filler': {
flex: 1
},
'& .webchat__basic-transcript__scrollable': {
display: 'flex',
flexDirection: 'column',
overflowX: 'hidden',
WebkitOverflowScrolling: 'touch'
},
'& .webchat__basic-transcript__transcript': {
listStyleType: 'none'
}
}
};
function validateAllActivitiesTagged(activities, bins) {
return activities.every(function (activity) {
return bins.some(function (bin) {
return bin.includes(activity);
});
});
}
var InternalTranscript = function InternalTranscript(_ref) {
var activityElementsRef = _ref.activityElementsRef,
className = _ref.className;
var _useStyleSet = (0, _useStyleSet5.default)(),
_useStyleSet2 = _slicedToArray(_useStyleSet, 1),
basicTranscriptStyleSet = _useStyleSet2[0].basicTranscript;
var _useStyleOptions = useStyleOptions(),
_useStyleOptions2 = _slicedToArray(_useStyleOptions, 1),
_useStyleOptions2$ = _useStyleOptions2[0],
bubbleFromUserNubOffset = _useStyleOptions2$.bubbleFromUserNubOffset,
bubbleNubOffset = _useStyleOptions2$.bubbleNubOffset,
groupTimestamp = _useStyleOptions2$.groupTimestamp,
internalLiveRegionFadeAfter = _useStyleOptions2$.internalLiveRegionFadeAfter,
showAvatarInGroup = _useStyleOptions2$.showAvatarInGroup;
var _useState = (0, _react.useState)(),
_useState2 = _slicedToArray(_useState, 2),
focusedActivityKey = _useState2[0],
setFocusedActivityKey = _useState2[1];
var _useActivities = useActivities(),
_useActivities2 = _slicedToArray(_useActivities, 1),
activities = _useActivities2[0];
var _useDirection = useDirection(),
_useDirection2 = _slicedToArray(_useDirection, 1),
direction = _useDirection2[0];
var createActivityRenderer = useCreateActivityRenderer();
var createActivityStatusRenderer = useCreateActivityStatusRenderer();
var createAvatarRenderer = useCreateAvatarRenderer();
var focus = (0, _useFocus.default)();
var groupActivities = useGroupActivities();
var localize = useLocalizer();
var rootClassName = (0, _useStyleToEmotionObject.default)()(ROOT_STYLE) + '';
var rootElementRef = (0, _react.useRef)();
var terminatorRef = (0, _react.useRef)();
var activityInteractiveAlt = localize('ACTIVITY_INTERACTIVE_LABEL_ALT');
var terminatorText = localize('TRANSCRIPT_TERMINATOR_TEXT');
var transcriptAriaLabel = localize('TRANSCRIPT_ARIA_LABEL_ALT');
var transcriptRoleDescription = localize('TRANSCRIPT_ARIA_ROLE_ALT');
var hideAllTimestamps = groupTimestamp === false; // Gets renderer for every activity.
// Activities that are not visible will return a falsy renderer.
// Converted from createActivityRenderer({ activity, nextVisibleActivity }) to createActivityRenderer(activity, nextVisibleActivity).
// This is for the memoization function to cache the arguments. Memoizer can only cache literal arguments.
var createActivityRendererWithLiteralArgs = (0, _react.useCallback)(function (activity, nextVisibleActivity) {
return createActivityRenderer({
activity: activity,
nextVisibleActivity: nextVisibleActivity
});
}, [createActivityRenderer]); // Create a memoized context of the createActivityRenderer function.
var activitiesWithRenderer = (0, _useMemoize.default)(createActivityRendererWithLiteralArgs, function (createActivityRendererWithLiteralArgsMemoized) {
// All calls to createActivityRendererWithLiteralArgsMemoized() in this function will be memoized (LRU = 1).
// In the next render cycle, calls to createActivityRendererWithLiteralArgsMemoized() might return the memoized result instead.
// This is an improvement to React useMemo(), because it only allows 1 memoization.
// useMemoize() allows any number of memoization.
var activitiesWithRenderer = [];
var nextVisibleActivity;
for (var index = activities.length - 1; index >= 0; index--) {
var activity = activities[index];
var renderActivity = createActivityRendererWithLiteralArgsMemoized(activity, nextVisibleActivity);
if (renderActivity) {
activitiesWithRenderer.splice(0, 0, {
activity: activity,
renderActivity: renderActivity
});
nextVisibleActivity = activity;
}
}
return activitiesWithRenderer;
}, [activities]);
var visibleActivities = (0, _react.useMemo)(function () {
return activitiesWithRenderer.map(function (_ref2) {
var activity = _ref2.activity;
return activity;
});
}, [activitiesWithRenderer]); // Tag activities based on types.
// The default implementation tag into 2 types: sender and status.
var _useMemo = (0, _react.useMemo)(function () {
var _groupActivities = groupActivities({
activities: visibleActivities
}),
activitiesGroupBySender = _groupActivities.sender,
activitiesGroupByStatus = _groupActivities.status;
if (!validateAllActivitiesTagged(visibleActivities, activitiesGroupBySender)) {
console.warn('botframework-webchat: Not every activities are grouped in the "sender" property. Please fix "groupActivitiesMiddleware" and group every activities.');
}
if (!validateAllActivitiesTagged(visibleActivities, activitiesGroupByStatus)) {
console.warn('botframework-webchat: Not every activities are grouped in the "status" property. Please fix "groupActivitiesMiddleware" and group every activities.');
}
return {
activitiesGroupBySender: activitiesGroupBySender,
activitiesGroupByStatus: activitiesGroupByStatus
};
}, [groupActivities, visibleActivities]),
activitiesGroupBySender = _useMemo.activitiesGroupBySender,
activitiesGroupByStatus = _useMemo.activitiesGroupByStatus; // Create a tree of activities with 2 dimensions: sender, followed by status.
var activityTree = (0, _react.useMemo)(function () {
var visibleActivitiesPendingGrouping = _toConsumableArray(visibleActivities);
var activityTree = [];
var _loop = function _loop() {
var _visibleActivitiesPen = _slicedToArray(visibleActivitiesPendingGrouping, 1),
activity = _visibleActivitiesPen[0];
var senderTree = [];
var activitiesWithSameSender = activitiesGroupBySender.find(function (activities) {
return activities.includes(activity);
});
activityTree.push(senderTree);
activitiesWithSameSender.forEach(function (activity) {
var activitiesWithSameStatus = activitiesGroupByStatus.find(function (activities) {
return activities.includes(activity);
});
var activitiesWithSameSenderAndStatus = (0, _intersectionOf.default)(visibleActivitiesPendingGrouping, activitiesWithSameSender, activitiesWithSameStatus);
if (activitiesWithSameSenderAndStatus.length) {
senderTree.push(activitiesWithSameSenderAndStatus);
_removeInline.default.apply(void 0, [visibleActivitiesPendingGrouping].concat(_toConsumableArray(activitiesWithSameSenderAndStatus)));
}
});
};
while (visibleActivitiesPendingGrouping.length) {
_loop();
} // Assertion: All activities in visibleActivities, must be assigned to the activityTree
if (!visibleActivities.every(function (activity) {
return activityTree.some(function (activitiesWithSameSender) {
return activitiesWithSameSender.some(function (activitiesWithSameSenderAndStatus) {
return activitiesWithSameSenderAndStatus.includes(activity);
});
});
})) {
console.warn('botframework-webchat internal: Not all visible activities are grouped in the activityTree.', {
visibleActivities: visibleActivities,
activityTree: activityTree
});
}
return activityTree;
}, [activitiesGroupBySender, activitiesGroupByStatus, visibleActivities]); // Flatten the tree back into an array with information related to rendering.
var renderingElements = (0, _react.useMemo)(function () {
var renderingElements = [];
var topSideBotNub = (0, _isZeroOrPositive.default)(bubbleNubOffset);
var topSideUserNub = (0, _isZeroOrPositive.default)(bubbleFromUserNubOffset);
activityTree.forEach(function (activitiesWithSameSender) {
var _activitiesWithSameSe = _slicedToArray(activitiesWithSameSender, 1),
_activitiesWithSameSe2 = _slicedToArray(_activitiesWithSameSe[0], 1),
firstActivity = _activitiesWithSameSe2[0];
var renderAvatar = createAvatarRenderer({
activity: firstActivity
});
activitiesWithSameSender.forEach(function (activitiesWithSameSenderAndStatus, indexWithinSenderGroup) {
var firstInSenderGroup = !indexWithinSenderGroup;
var lastInSenderGroup = indexWithinSenderGroup === activitiesWithSameSender.length - 1;
activitiesWithSameSenderAndStatus.forEach(function (activity, indexWithinSenderAndStatusGroup) {
// We only show the timestamp at the end of the sender group. But we always show the "Send failed, retry" prompt.
var renderActivityStatus = createActivityStatusRenderer({
activity: activity
});
var firstInSenderAndStatusGroup = !indexWithinSenderAndStatusGroup;
var lastInSenderAndStatusGroup = indexWithinSenderAndStatusGroup === activitiesWithSameSenderAndStatus.length - 1;
var _activitiesWithRender = activitiesWithRenderer.find(function (entry) {
return entry.activity === activity;
}),
renderActivity = _activitiesWithRender.renderActivity;
var key = (0, _getActivityUniqueId.default)(activity) || renderingElements.length;
var _activity$channelData = activity.channelData;
_activity$channelData = _activity$channelData === void 0 ? {} : _activity$channelData;
var _activity$channelData2 = _activity$channelData.messageBack;
_activity$channelData2 = _activity$channelData2 === void 0 ? {} : _activity$channelData2;
var messageBackDisplayText = _activity$channelData2.displayText,
role = activity.from.role,
text = activity.text;
var topSideNub = role === 'user' ? topSideUserNub : topSideBotNub;
var showCallout; // Depends on different "showAvatarInGroup" setting, we will show the avatar in different positions.
if (showAvatarInGroup === 'sender') {
if (topSideNub) {
showCallout = firstInSenderGroup && firstInSenderAndStatusGroup;
} else {
showCallout = lastInSenderGroup && lastInSenderAndStatusGroup;
}
} else if (showAvatarInGroup === 'status') {
if (topSideNub) {
showCallout = firstInSenderAndStatusGroup;
} else {
showCallout = lastInSenderAndStatusGroup;
}
} else {
showCallout = true;
}
var focusActivity = function focusActivity() {
setFocusedActivityKey((0, _getActivityUniqueId.default)(activity)); // IE11 need to manually focus on the transcript.
var rootElement = rootElementRef.current;
rootElement && rootElement.focus();
};
renderingElements.push({
activity: activity,
// After the element is mounted, set it to activityElementsRef.
callbackRef: function callbackRef(activityElement) {
var entry = activityElementsRef.current.find(function (_ref3) {
var activityID = _ref3.activityID;
return activityID === activity.id;
});
if (entry) {
entry.element = activityElement;
}
},
// Calling this function will put the focus on the transcript and the activity.
focusActivity: focusActivity,
// When a child of the activity receives focus, notify the transcript to set the aria-activedescendant to this activity.
handleFocus: function handleFocus() {
setFocusedActivityKey((0, _getActivityUniqueId.default)(activity));
},
handleKeyDown: function handleKeyDown(event) {
if (event.key === 'Escape') {
event.preventDefault();
event.stopPropagation();
setFocusedActivityKey((0, _getActivityUniqueId.default)(activity));
var current = rootElementRef.current;
current && current.focus();
}
},
// For accessibility: when the user press up/down arrow keys, we put a visual focus indicator around the focused activity.
// We should do the same for mouse, that is why we have the click handler here.
// We are doing it in event capture phase to prevent other components from stopping event propagation to us.
handleMouseDownCapture: function handleMouseDownCapture(_ref4) {
var target = _ref4.target;
var tabIndex = (0, _getTabIndex.default)(target);
if (typeof tabIndex !== 'number' || tabIndex < 0 || target.getAttribute('aria-disabled') === 'true') {
focusActivity();
}
},
// "hideTimestamp" is a render-time parameter for renderActivityStatus().
// If true, it will hide the timestamp, but it will continue to show the
// retry prompt. And show the screen reader version of the timestamp.
hideTimestamp: hideAllTimestamps || indexWithinSenderAndStatusGroup !== activitiesWithSameSenderAndStatus.length - 1,
key: key,
// When "liveRegionKey" changes, it will show up in the live region momentarily.
liveRegionKey: key + '|' + (messageBackDisplayText || text),
renderActivity: renderActivity,
renderActivityStatus: renderActivityStatus,
renderAvatar: renderAvatar,
role: role,
// TODO: [P2] #2858 We should use core/definitions/speakingActivity for this predicate instead
shouldSpeak: activity.channelData && activity.channelData.speak,
showCallout: showCallout
});
});
});
});
var activityElements = activityElementsRef.current; // Update activityElementRef with new sets of activity, while retaining the existing referencing element if exists.
activityElementsRef.current = renderingElements.map(function (_ref5) {
var activity = _ref5.activity,
id = _ref5.activity.id,
elementId = _ref5.elementId,
key = _ref5.key;
var existingEntry = activityElements.find(function (entry) {
return entry.key === key;
});
return {
activity: activity,
activityID: id,
ariaLabelID: existingEntry ? existingEntry.ariaLabelID : "webchat__basic-transcript__activity-label-".concat((0, _mathRandom.default)().toString(36).substr(2, 5)),
element: existingEntry && existingEntry.element,
elementId: elementId,
key: key
};
}); // There must be one focused (a.k.a. aria-activedescendant) designated. We default it to the last one.
if (!renderingElements.find(function (_ref6) {
var focused = _ref6.focused;
return focused;
})) {
var lastElement = renderingElements[renderingElements.length - 1];
if (lastElement) {
lastElement.focused = true;
}
}
return renderingElements;
}, [activitiesWithRenderer, activityElementsRef, activityTree, bubbleFromUserNubOffset, bubbleNubOffset, createActivityStatusRenderer, createAvatarRenderer, hideAllTimestamps, rootElementRef, showAvatarInGroup]);
var renderingActivities = (0, _react.useMemo)(function () {
return renderingElements.map(function (_ref7) {
var activity = _ref7.activity;
return activity;
});
}, [renderingElements]);
var scrollToBottomScrollTo = (0, _reactScrollToBottom.useScrollTo)();
var scrollToBottomScrollToEnd = (0, _reactScrollToBottom.useScrollToEnd)();
var scrollTo = (0, _react.useCallback)(function (position) {
var _ref8 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
_ref8$behavior = _ref8.behavior,
behavior = _ref8$behavior === void 0 ? 'auto' : _ref8$behavior;
if (!position) {
throw new Error('botframework-webchat: First argument passed to "useScrollTo" must be a ScrollPosition object.');
}
var activityID = position.activityID,
scrollTop = position.scrollTop;
if (typeof scrollTop !== 'undefined') {
scrollToBottomScrollTo(scrollTop, {
behavior: behavior
});
} else if (typeof activityID !== 'undefined') {
var rootElement = rootElementRef.current;
var _ref9 = activityElementsRef.current.find(function (entry) {
return entry.activityID === activityID;
}) || {},
activityElement = _ref9.element;
var scrollableElement = rootElement.querySelector('.webchat__basic-transcript__scrollable');
if (scrollableElement && activityElement) {
var _activityElement$getC = activityElement.getClientRects(),
_activityElement$getC2 = _slicedToArray(_activityElement$getC, 1),
_activityElement$getC3 = _activityElement$getC2[0],
activityElementHeight = _activityElement$getC3.height,
activityElementY = _activityElement$getC3.y;
var _scrollableElement$ge = scrollableElement.getClientRects(),
_scrollableElement$ge2 = _slicedToArray(_scrollableElement$ge, 1),
scrollableHeight = _scrollableElement$ge2[0].height;
var activityElementOffsetTop = activityElementY + scrollableElement.scrollTop;
var _scrollTop = Math.min(activityElementOffsetTop, activityElementOffsetTop - scrollableHeight + activityElementHeight);
scrollToBottomScrollTo(_scrollTop, {
behavior: behavior
});
}
}
}, [activityElementsRef, rootElementRef, scrollToBottomScrollTo]);
var scrollRelative = (0, _react.useCallback)(function (direction) {
var _ref10 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
displacement = _ref10.displacement;
var rootElement = rootElementRef.current;
if (!rootElement) {
return;
}
var scrollable = rootElement.querySelector('.webchat__basic-transcript__scrollable');
var nextScrollTop;
if (typeof displacement === 'number') {
nextScrollTop = scrollable.scrollTop + (direction === 'down' ? 1 : -1) * displacement;
} else {
nextScrollTop = scrollable.scrollTop + (direction === 'down' ? 1 : -1) * scrollable.offsetHeight;
}
scrollTo({
scrollTop: Math.max(0, Math.min(scrollable.scrollHeight - scrollable.offsetHeight, nextScrollTop))
}, {
behavior: 'smooth'
});
}, [rootElementRef, scrollTo]); // Since there could be multiple instances of <BasicTranscript> inside the <Composer>, when the developer calls `scrollXXX`, we need to call it on all instances.
// We call `useRegisterScrollXXX` to register a callback function, the `useScrollXXX` will multiplex the call into each instance of <BasicTranscript>.
(0, _useRegisterScrollTo.default)(scrollTo);
(0, _useRegisterScrollToEnd.default)(scrollToBottomScrollToEnd);
(0, _useRegisterScrollRelative.default)(scrollRelative);
var dispatchScrollPosition = (0, _useDispatchScrollPosition.default)();
var patchedDispatchScrollPosition = (0, _react.useMemo)(function () {
if (!dispatchScrollPosition) {
return;
}
return function (_ref11) {
var scrollTop = _ref11.scrollTop;
var rootElement = rootElementRef.current;
if (!rootElement) {
return;
}
var scrollableElement = rootElement.querySelector('.webchat__basic-transcript__scrollable');
var _scrollableElement$ge3 = scrollableElement.getClientRects(),
_scrollableElement$ge4 = _slicedToArray(_scrollableElement$ge3, 1),
_scrollableElement$ge5 = _scrollableElement$ge4[0];
_scrollableElement$ge5 = _scrollableElement$ge5 === void 0 ? {} : _scrollableElement$ge5;
var offsetHeight = _scrollableElement$ge5.height; // Find the activity just above scroll view bottom.
// If the scroll view is already on top, get the first activity.
var entry = scrollableElement.scrollTop ? _toConsumableArray(activityElementsRef.current).reverse().find(function (_ref12) {
var element = _ref12.element;
if (!element) {
return false;
}
var _element$getClientRec = element.getClientRects(),
_element$getClientRec2 = _slicedToArray(_element$getClientRec, 1),
_element$getClientRec3 = _element$getClientRec2[0];
_element$getClientRec3 = _element$getClientRec3 === void 0 ? {} : _element$getClientRec3;
var y = _element$getClientRec3.y;
return y < offsetHeight;
}) : activityElementsRef.current[0];
var _ref13 = entry || {},
activityID = _ref13.activityID;
dispatchScrollPosition(_objectSpread(_objectSpread({}, activityID ? {
activityID: activityID
} : {}), {}, {
scrollTop: scrollTop
}));
};
}, [activityElementsRef, dispatchScrollPosition, rootElementRef]);
(0, _reactScrollToBottom.useObserveScrollPosition)(patchedDispatchScrollPosition);
var _useAcknowledgedActiv = (0, _useAcknowledgedActivity.default)(),
_useAcknowledgedActiv2 = _slicedToArray(_useAcknowledgedActiv, 1),
lastInteractedActivity = _useAcknowledgedActiv2[0];
var indexOfLastInteractedActivity = activities.indexOf(lastInteractedActivity); // Create a new ID for aria-activedescendant every time the active descendant change.
// In our design, the transcript will only have 1 focused activity and it has an ID. Other blurred activities will not have ID assigned.
// This help with performance.
// But browser usually do noop if the value of aria-activedescendant doesn't change.
// That means, if we assign the same ID to another element, browser will do noop.
// We need to generate a new ID so the browser see there is a change in aria-activedescendant value and perform accordingly.
var activeDescendantElementId = (0, _react.useMemo)(function () {
return focusedActivityKey && "webchat__basic-transcript__active-descendant-".concat((0, _mathRandom.default)().toString(36).substr(2, 5));
}, [focusedActivityKey]);
var scrollActiveDescendantIntoView = (0, _react.useCallback)(function () {
var activeDescendant = activeDescendantElementId && document.getElementById(activeDescendantElementId); // Don't scroll active descendant into view if the focus is already inside it.
// Otherwise, given the focus is on the send box, clicking on any <input> inside the Adaptive Cards may cause the view to move.
// This UX is not desirable because click should not cause scroll.
if (activeDescendant && !activeDescendant.contains(document.activeElement)) {
// Checks if scrollIntoView support options or not.
// - https://github.com/Modernizr/Modernizr/issues/1568#issuecomment-419457972
// - https://stackoverflow.com/questions/46919627/is-it-possible-to-test-for-scrollintoview-browser-compatibility
if ('scrollBehavior' in document.documentElement.style) {
activeDescendant.scrollIntoView({
block: 'nearest'
});
} else {
// This is for browser that does not support options passed to scrollIntoView(), possibly IE11.
var scrollableElement = rootElementRef.current.querySelector('.webchat__basic-transcript__scrollable');
var scrollTopAtTopSide = activeDescendant.offsetTop;
var scrollTopAtBottomSide = activeDescendant.offsetTop + activeDescendant.offsetHeight;
if (scrollTopAtTopSide < scrollableElement.scrollTop) {
scrollableElement.scrollTop = scrollTopAtTopSide;
} else if (scrollTopAtBottomSide > scrollableElement.scrollTop + scrollableElement.offsetHeight) {
scrollableElement.scrollTop = scrollTopAtBottomSide - scrollableElement.offsetHeight;
}
}
}
}, [activeDescendantElementId, rootElementRef]);
var handleTranscriptFocus = (0, _react.useCallback)(function (event) {
var currentTarget = event.currentTarget,
target = event.target; // When focus is placed on the transcript, scroll active descendant into the view.
currentTarget === target && scrollActiveDescendantIntoView();
}, [scrollActiveDescendantIntoView]); // After new aria-activedescendant is assigned, we will need to scroll it into view.
// User agent will scroll automatically for focusing element, but not for aria-activedescendant.
// We need to do the scrolling manually.
(0, _react.useEffect)(function () {
return scrollActiveDescendantIntoView();
}, [scrollActiveDescendantIntoView]); // If any activities has changed, reset the active descendant.
(0, _react.useEffect)(function () {
return setFocusedActivityKey(undefined);
}, [activities, setFocusedActivityKey]);
var focusRelativeActivity = (0, _react.useCallback)(function (delta) {
if (isNaN(delta) || !renderingElements.length) {
return setFocusedActivityKey(undefined);
}
var index = renderingElements.findIndex(function (_ref14) {
var key = _ref14.key;
return key === focusedActivityKey;
});
var nextIndex = ~index ? Math.max(0, Math.min(renderingElements.length - 1, index + delta)) : renderingElements.length - 1;
var nextFocusedActivity = renderingElements[nextIndex];
setFocusedActivityKey(nextFocusedActivity.key);
rootElementRef.current && rootElementRef.current.focus();
}, [focusedActivityKey, renderingElements, rootElementRef, setFocusedActivityKey]);
var handleTranscriptKeyDown = (0, _react.useCallback)(function (event) {
var target = event.target;
var fromEndOfTranscriptIndicator = target === terminatorRef.current;
var fromTranscript = target === event.currentTarget;
if (!fromEndOfTranscriptIndicator && !fromTranscript) {
return;
}
var handled = true;
switch (event.key) {
case 'ArrowDown':
focusRelativeActivity(fromEndOfTranscriptIndicator ? 0 : 1);
break;
case 'ArrowUp':
focusRelativeActivity(fromEndOfTranscriptIndicator ? 0 : -1);
break;
case 'End':
focusRelativeActivity(Infinity);
break;
case 'Enter':
if (!fromEndOfTranscriptIndicator) {
var focusedActivityEntry = renderingElements.find(function (_ref15) {
var key = _ref15.key;
return key === focusedActivityKey;
});
if (focusedActivityEntry) {
var _ref16 = activityElementsRef.current.find(function (_ref17) {
var activity = _ref17.activity;
return activity === focusedActivityEntry.activity;
}) || {},
focusedActivityElement = _ref16.element;
if (focusedActivityElement) {
var _tabbableElements$fil = (0, _tabbableElements.default)(focusedActivityElement).filter(function (_ref18) {
var className = _ref18.className;
return className !== 'webchat__basic-transcript__activity-sentinel';
}),
_tabbableElements$fil2 = _slicedToArray(_tabbableElements$fil, 1),
firstTabbableElement = _tabbableElements$fil2[0];
firstTabbableElement && firstTabbableElement.focus();
}
}
}
break;
case 'Escape':
focus('sendBoxWithoutKeyboard');
break;
case 'Home':
focusRelativeActivity(-Infinity);
break;
default:
handled = false;
break;
}
if (handled) {
event.preventDefault(); // If a custom HTML control wants to handle up/down arrow, we will prevent them from listening to this event to prevent bugs due to handling arrow keys twice.
event.stopPropagation();
}
}, [focusedActivityKey, activityElementsRef, focusRelativeActivity, focus, terminatorRef, renderingElements]);
var labelId = (0, _useUniqueId.default)('webchat__basic-transcript__label'); // If SHIFT-TAB from "End of transcript" indicator, if focusedActivityKey is not set (or no longer exists), set it the the bottommost activity.
var setBottommostFocusedActivityKeyIfNeeded = (0, _react.useCallback)(function () {
if (!~renderingElements.findIndex(function (_ref19) {
var key = _ref19.key;
return key === focusedActivityKey;
})) {
var _ref20 = renderingElements[renderingElements.length - 1] || {},
lastActivityKey = _ref20.key;
setFocusedActivityKey(lastActivityKey);
}
}, [focusedActivityKey, renderingElements, setFocusedActivityKey]);
var handleTranscriptKeyDownCapture = (0, _react.useCallback)(function (event) {
var altKey = event.altKey,
ctrlKey = event.ctrlKey,
key = event.key,
metaKey = event.metaKey,
target = event.target;
if (altKey || ctrlKey && key !== 'v' || metaKey || !(0, _inputtableKey.default)(key) && key !== 'Backspace') {
// Ignore if one of the utility key (except SHIFT) is pressed
// E.g. CTRL-C on a link in one of the message should not jump to chat box
// E.g. "A" or "Backspace" should jump to chat box
return;
} // Send keystrokes to send box if we are focusing on the transcript or terminator.
if (target === event.currentTarget || target === terminatorRef.current) {
event.stopPropagation();
focus('sendBox');
}
}, [focus]);
var focusTranscriptCallback = (0, _react.useCallback)(function () {
return rootElementRef.current && rootElementRef.current.focus();
}, [rootElementRef]);
(0, _useRegisterFocusTranscript.default)(focusTranscriptCallback);
var handleFocusActivity = (0, _react.useCallback)(function (key) {
setFocusedActivityKey(key);
rootElementRef.current && rootElementRef.current.focus();
}, [setFocusedActivityKey]); // When the focusing activity has changed, dispatch an event to observers of "useObserveTranscriptFocus".
var dispatchTranscriptFocus = (0, _useDispatchTranscriptFocus.default)();
var focusedActivity = (0, _react.useMemo)(function () {
var _ref21 = renderingElements.find(function (_ref22) {
var key = _ref22.key;
return key === focusedActivityKey;
}) || {},
activity = _ref21.activity;
return activity;
}, [focusedActivityKey, renderingElements]);
(0, _react.useMemo)(function () {
return dispatchTranscriptFocus && dispatchTranscriptFocus({
activity: focusedActivity
});
}, [dispatchTranscriptFocus, focusedActivity]); // This is required by IE11.
// When the user clicks on and empty space (a.k.a. filler) in an empty transcript, IE11 says the focus is on the <div className="filler">,
// despite the fact there are no "tabIndex" attributes set on the filler.
// We need to artificially send the focus back to the transcript.
var handleFocusFiller = (0, _react.useCallback)(function () {
var current = rootElementRef.current;
current && current.focus();
}, [rootElementRef]);
return /*#__PURE__*/_react.default.createElement("div", {
"aria-activedescendant": focusedActivityKey ? activeDescendantElementId : undefined,
"aria-labelledby": labelId,
className: (0, _classnames.default)('webchat__basic-transcript', basicTranscriptStyleSet + '', rootClassName, (className || '') + ''),
dir: direction,
onFocus: handleTranscriptFocus,
onKeyDown: handleTranscriptKeyDown,
onKeyDownCapture: handleTranscriptKeyDownCapture,
ref: rootElementRef // "aria-activedescendant" will only works with a number of roles and it must be explicitly set.
// https://www.w3.org/TR/wai-aria/#aria-activedescendant
,
role: "group" // For up/down arrow key navigation across activities, this component must be included in the tab sequence.
// Otherwise, "aria-activedescendant" will not be narrated when the user press up/down arrow keys.
// https://www.w3.org/TR/wai-aria-practices-1.1/#kbd_focus_activedescendant
,
tabIndex: 0
}, /*#__PURE__*/_react.default.createElement(_ScreenReaderText.default, {
id: labelId,
text: transcriptAriaLabel
}), /*#__PURE__*/_react.default.createElement("section", {
"aria-atomic": false,
"aria-live": "polite",
"aria-relevant": "additions",
"aria-roledescription": transcriptRoleDescription,
role: "log"
}, renderingElements.map(function (_ref23) {
var activity = _ref23.activity,
liveRegionKey = _ref23.liveRegionKey;
return /*#__PURE__*/_react.default.createElement(_Fade.default, {
fadeAfter: internalLiveRegionFadeAfter,
key: liveRegionKey
}, function () {
return /*#__PURE__*/_react.default.createElement(_ScreenReaderActivity.default, {
activity: activity
});
});
})), /*#__PURE__*/_react.default.createElement(InternalTranscriptScrollable, {
activities: renderingActivities,
onFocusActivity: handleFocusActivity,
onFocusFiller: handleFocusFiller,
terminatorRef: terminatorRef
}, renderingElements.map(function (_ref24, index) {
var activity = _ref24.activity,
callbackRef = _ref24.callbackRef,
focusActivity = _ref24.focusActivity,
handleFocus = _ref24.handleFocus,
handleKeyDown = _ref24.handleKeyDown,
handleMouseDownCapture = _ref24.handleMouseDownCapture,
hideTimestamp = _ref24.hideTimestamp,
key = _ref24.key,
renderActivity = _ref24.renderActivity,
renderActivityStatus = _ref24.renderActivityStatus,
renderAvatar = _ref24.renderAvatar,
role = _ref24.role,
shouldSpeak = _ref24.shouldSpeak,
showCallout = _ref24.showCallout;
var _ref25 = activityElementsRef.current.find(function (entry) {
return entry.activity === activity;
}) || {},
ariaLabelID = _ref25.ariaLabelID,
element = _ref25.element;
var activeDescendant = focusedActivityKey === key;
var isContentInteractive = !!(element ? (0, _tabbableElements.default)(element.querySelector('.webchat__basic-transcript__activity-box')).length : 0);
return /*#__PURE__*/_react.default.createElement("li", {
"aria-labelledby": ariaLabelID,
className: (0, _classnames.default)('webchat__basic-transcript__activity', {
'webchat__basic-transcript__activity--acknowledged': index <= indexOfLastInteractedActivity,
'webchat__basic-transcript__activity--from-bot': role !== 'user',
'webchat__basic-transcript__activity--from-user': role === 'user'
}) // Set "id" for valid for accessibility.
/* eslint-disable-next-line react/forbid-dom-props */
,
id: activeDescendant ? activeDescendantElementId : undefined,
key: key,
onFocus: handleFocus,
onKeyDown: handleKeyDown,
onMouseDownCapture: handleMouseDownCapture,
ref: callbackRef
}, /*#__PURE__*/_react.default.createElement(_ScreenReaderActivity.default, {
activity: activity,
id: ariaLabelID,
renderAttachments: false
}, !!isContentInteractive && /*#__PURE__*/_react.default.createElement("p", null, activityInteractiveAlt)), /*#__PURE__*/_react.default.createElement(_FocusRedirector.default, {
className: "webchat__basic-transcript__activity-sentinel",
onFocus: focusActivity,
redirectRef: rootElementRef
}), /*#__PURE__*/_react.default.createElement("div", {
className: "webchat__basic-transcript__activity-box"
}, renderActivity({
hideTimestamp: hideTimestamp,
renderActivityStatus: renderActivityStatus,
renderAvatar: renderAvatar,
showCallout: showCallout
})), shouldSpeak && /*#__PURE__*/_react.default.createElement(_Speak.default, {
activity: activity
}), /*#__PURE__*/_react.default.createElement(_FocusRedirector.default, {
className: "webchat__basic-transcript__activity-sentinel",
onFocus: focusActivity,
redirectRef: rootElementRef
}), /*#__PURE__*/_react.default.createElement("div", {
className: (0, _classnames.default)('webchat__basic-transcript__activity-indicator', {
'webchat__basic-transcript__activity-indicator--first': !index,
'webchat__basic-transcript__activity-indicator--focus': activeDescendant
})
}));
})), !!renderingElements.length && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_FocusRedirector.default, {
className: "webchat__basic-transcript__sentinel",
onFocus: setBottommostFocusedActivityKeyIfNeeded,
redirectRef: rootElementRef
}), /*#__PURE__*/_react.default.createElement("div", {
className: "webchat__basic-transcript__terminator",
ref: terminatorRef,
tabIndex: 0
}, /*#__PURE__*/_react.default.createElement("div", {
className: "webchat__basic-transcript__terminator-body"
}, /*#__PURE__*/_react.default.createElement("div", {
className: "webchat__basic-transcript__terminator-text"
}, terminatorText)))), /*#__PURE__*/_react.default.createElement("div", {
className: "webchat__basic-transcript__focus-indicator"
}));
};
InternalTranscript.defaultProps = {
className: ''
};
InternalTranscript.propTypes = {
activityElementsRef: _propTypes.default.shape({
current: _propTypes.default.array.isRequired
}).isRequired,
className: _propTypes.default.string
};
var InternalScreenReaderTranscript = function InternalScreenReaderTranscript(_ref26) {
var renderingElements = _ref26.renderingElements;
var localize = useLocalizer();
var _useStyleOptions3 = useStyleOptions(),
_useStyleOptions4 = _slicedToArray(_useStyleOptions3, 1),
internalLiveRegionFadeAfter = _useStyleOptions4[0];
var transcriptRoleDescription = localize('TRANSCRIPT_ARIA_ROLE_ALT');
return /*#__PURE__*/_react.default.createElement("section", {
"aria-atomic": false,
"aria-live": "polite",
"aria-relevant": "additions",
"aria-roledescription": transcriptRoleDescription,
role: "log"
}, renderingElements.map(function (_ref27) {
var activity = _ref27.activity,
liveRegionKey = _ref27.liveRegionKey;
return /*#__PURE__*/_react.default.createElement(_Fade.default, {
fadeAfter: internalLiveRegionFadeAfter,
key: liveRegionKey
}, function () {
return /*#__PURE__*/_react.default.createElement(_ScreenReaderActivity.default, {
activity: activity
});
});
}));
};
InternalScreenReaderTranscript.propTypes = {
renderingElements: _propTypes.default.arrayOf(_propTypes.default.shape({
activity: _propTypes.default.any,
liveRegionKey: _propTypes.default.string
})).isRequired
}; // Separating high-frequency hooks to improve performance.
var InternalTranscriptScrollable = function InternalTranscriptScrollable(_ref28) {
var activities = _ref28.activities,
children = _ref28.children,
onFocusActivity = _ref28.onFocusActivity,
onFocusFiller = _ref28.onFocusFiller,
terminatorRef = _ref28.terminatorRef;
var _useStyleSet3 = (0, _useStyleSet5.default)(),
_useStyleSet4 = _slicedToArray(_useStyleSet3, 1),
activitiesStyleSet = _useStyleSet4[0].activities;
var _useStyleOptions5 = useStyleOptions(),
_useStyleOptions6 = _slicedToArray(_useStyleOptions5, 1),
hideScrollToEndButton = _useStyleOptions6[0].hideScrollToEndButton;
var _useAnimatingToEnd = (0, _reactScrollToBottom.useAnimatingToEnd)(),
_useAnimatingToEnd2 = _slicedToArray(_useAnimatingToEnd, 1),
animatingToEnd = _useAnimatingToEnd2[0];
var _useSticky = (0, _reactScrollToBottom.useSticky)(),
_useSticky2 = _slicedToArray(_useSticky, 1),
sticky = _useSticky2[0];
var lastVisibleActivityId = (0, _getActivityUniqueId.default)(activities[activities.length - 1] || {}); // Activity ID of the last visible activity in the list.
var localize = useLocalizer();
var scrollToEndButtonRef = (0, _react.useRef)();
var lastReadActivityIdRef = (0, _react.useRef)(lastVisibleActivityId);
var transcriptRoleDescription = localize('TRANSCRIPT_ARIA_ROLE_ALT');
var allActivitiesRead = lastVisibleActivityId === lastReadActivityIdRef.current;
var handleScrollToEndButtonClick = (0, _react.useCallback)(function () {
// After the "New message" button is clicked, focus on the first unread activity.
var index = activities.findIndex(function (_ref29) {
var id = _ref29.id;
return id === lastReadActivityIdRef.current;
});
if (~index) {
var firstUnreadActivity = activities[index + 1];
if (firstUnreadActivity) {
return onFocusActivity((0, _getActivityUniqueId.default)(firstUnreadActivity));
}
}
var current = terminatorRef.current;
current && current.focus();
}, [activities, lastReadActivityIdRef, onFocusActivity, terminatorRef]);
if (sticky) {
// If it is sticky, the user is at the bottom of the transcript, everything is read.
// So mark the activity ID as read.
lastReadActivityIdRef.current = lastVisibleActivityId;
} // Finds where we should render the "New messages" button, in in