[MM-56599] Upgrade to "@floating-ui/react" for advanced_text_editor/formatting_bar component (#25970)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
62064e3bf7
Коммит
797fe9b917
@@ -6,6 +6,7 @@
|
|||||||
"version": "9.3.0",
|
"version": "9.3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@floating-ui/react": "0.26.6",
|
||||||
"@floating-ui/react-dom": "1.0.0",
|
"@floating-ui/react-dom": "1.0.0",
|
||||||
"@floating-ui/react-dom-interactions": "0.10.3",
|
"@floating-ui/react-dom-interactions": "0.10.3",
|
||||||
"@giphy/js-fetch-api": "5.1.0",
|
"@giphy/js-fetch-api": "5.1.0",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {useFloating, offset} from '@floating-ui/react-dom';
|
import {useFloating, offset, useClick, useDismiss, useInteractions} from '@floating-ui/react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React, {memo, useCallback, useEffect, useRef, useState} from 'react';
|
import React, {memo, useCallback, useEffect, useRef, useState} from 'react';
|
||||||
import {useIntl} from 'react-intl';
|
import {useIntl} from 'react-intl';
|
||||||
@@ -13,7 +13,7 @@ import {DotsHorizontalIcon} from '@mattermost/compass-icons/components';
|
|||||||
import type {ApplyMarkdownOptions} from 'utils/markdown/apply_markdown';
|
import type {ApplyMarkdownOptions} from 'utils/markdown/apply_markdown';
|
||||||
|
|
||||||
import FormattingIcon, {IconContainer} from './formatting_icon';
|
import FormattingIcon, {IconContainer} from './formatting_icon';
|
||||||
import {useFormattingBarControls, useGetLatest} from './hooks';
|
import {useFormattingBarControls} from './hooks';
|
||||||
|
|
||||||
export const Separator = styled.div`
|
export const Separator = styled.div`
|
||||||
display: block;
|
display: block;
|
||||||
@@ -147,38 +147,22 @@ const FormattingBar = (props: FormattingBarProps): JSX.Element => {
|
|||||||
const {formatMessage} = useIntl();
|
const {formatMessage} = useIntl();
|
||||||
const HiddenControlsButtonAriaLabel = formatMessage({id: 'accessibility.button.hidden_controls_button', defaultMessage: 'show hidden formatting options'});
|
const HiddenControlsButtonAriaLabel = formatMessage({id: 'accessibility.button.hidden_controls_button', defaultMessage: 'show hidden formatting options'});
|
||||||
|
|
||||||
const {x, y, reference, floating, strategy, update, refs: {reference: buttonRef, floating: floatingRef}} = useFloating<HTMLButtonElement>({
|
const {x, y, strategy, update, context, refs: {setReference, setFloating}} = useFloating<HTMLButtonElement>({
|
||||||
|
open: showHiddenControls,
|
||||||
|
onOpenChange: setShowHiddenControls,
|
||||||
placement: 'top',
|
placement: 'top',
|
||||||
middleware: [offset({mainAxis: 4})],
|
middleware: [offset({mainAxis: 4})],
|
||||||
});
|
});
|
||||||
|
|
||||||
// this little helper hook always returns the latest refs and does not mess with the popper placement calculation
|
const click = useClick(context);
|
||||||
const getLatest = useGetLatest({
|
const {getReferenceProps: getClickReferenceProps, getFloatingProps: getClickFloatingProps} = useInteractions([
|
||||||
showHiddenControls,
|
click,
|
||||||
buttonRef,
|
]);
|
||||||
floatingRef,
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
const dismiss = useDismiss(context);
|
||||||
const handleClickOutside: EventListener = (event) => {
|
const {getReferenceProps: getDismissReferenceProps, getFloatingProps: getDismissFloatingProps} = useInteractions([
|
||||||
const {floatingRef, buttonRef} = getLatest();
|
dismiss,
|
||||||
const target = event.composedPath?.()?.[0] || event.target;
|
]);
|
||||||
if (target instanceof Node) {
|
|
||||||
if (
|
|
||||||
floatingRef !== null &&
|
|
||||||
buttonRef !== null &&
|
|
||||||
!floatingRef.current?.contains(target) &&
|
|
||||||
!buttonRef.current?.contains(target)
|
|
||||||
) {
|
|
||||||
setShowHiddenControls(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener('mousedown', handleClickOutside);
|
|
||||||
|
|
||||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
||||||
}, [getLatest, setShowHiddenControls]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
update?.();
|
update?.();
|
||||||
@@ -186,13 +170,6 @@ const FormattingBar = (props: FormattingBarProps): JSX.Element => {
|
|||||||
|
|
||||||
const hasHiddenControls = wideMode !== 'wide';
|
const hasHiddenControls = wideMode !== 'wide';
|
||||||
|
|
||||||
const toggleHiddenControls = useCallback((event?) => {
|
|
||||||
if (event) {
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
setShowHiddenControls(!showHiddenControls);
|
|
||||||
}, [showHiddenControls]);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* wrapping this factory in useCallback prevents it from constantly getting a new
|
* wrapping this factory in useCallback prevents it from constantly getting a new
|
||||||
* function signature as if we would define it directly in the props of
|
* function signature as if we would define it directly in the props of
|
||||||
@@ -222,9 +199,9 @@ const FormattingBar = (props: FormattingBarProps): JSX.Element => {
|
|||||||
|
|
||||||
// if hidden controls are currently open close them
|
// if hidden controls are currently open close them
|
||||||
if (showHiddenControls) {
|
if (showHiddenControls) {
|
||||||
toggleHiddenControls();
|
setShowHiddenControls(true);
|
||||||
}
|
}
|
||||||
}, [getCurrentSelection, getCurrentMessage, applyMarkdown, showHiddenControls, toggleHiddenControls, disableControls]);
|
}, [getCurrentSelection, getCurrentMessage, applyMarkdown, showHiddenControls, disableControls]);
|
||||||
|
|
||||||
const leftPosition = wideMode === 'min' ? (x ?? 0) + DEFAULT_MIN_MODE_X_COORD : x ?? 0;
|
const leftPosition = wideMode === 'min' ? (x ?? 0) + DEFAULT_MIN_MODE_X_COORD : x ?? 0;
|
||||||
|
|
||||||
@@ -263,10 +240,11 @@ const FormattingBar = (props: FormattingBarProps): JSX.Element => {
|
|||||||
<>
|
<>
|
||||||
<IconContainer
|
<IconContainer
|
||||||
id={'HiddenControlsButton' + location}
|
id={'HiddenControlsButton' + location}
|
||||||
ref={reference}
|
ref={setReference}
|
||||||
className={classNames({active: showHiddenControls})}
|
className={classNames({active: showHiddenControls})}
|
||||||
onClick={toggleHiddenControls}
|
|
||||||
aria-label={HiddenControlsButtonAriaLabel}
|
aria-label={HiddenControlsButtonAriaLabel}
|
||||||
|
{...getClickReferenceProps()}
|
||||||
|
{...getDismissReferenceProps()}
|
||||||
>
|
>
|
||||||
<DotsHorizontalIcon
|
<DotsHorizontalIcon
|
||||||
color={'currentColor'}
|
color={'currentColor'}
|
||||||
@@ -282,8 +260,10 @@ const FormattingBar = (props: FormattingBarProps): JSX.Element => {
|
|||||||
in={showHiddenControls}
|
in={showHiddenControls}
|
||||||
>
|
>
|
||||||
<HiddenControlsContainer
|
<HiddenControlsContainer
|
||||||
ref={floating}
|
ref={setFloating}
|
||||||
style={hiddenControlsContainerStyles}
|
style={hiddenControlsContainerStyles}
|
||||||
|
{...getClickFloatingProps()}
|
||||||
|
{...getDismissFloatingProps()}
|
||||||
>
|
>
|
||||||
{hiddenControls.map((mode) => {
|
{hiddenControls.map((mode) => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -3,18 +3,13 @@
|
|||||||
|
|
||||||
import type {Instance} from '@popperjs/core';
|
import type {Instance} from '@popperjs/core';
|
||||||
import {debounce} from 'lodash';
|
import {debounce} from 'lodash';
|
||||||
import React, {useCallback, useEffect, useLayoutEffect, useState} from 'react';
|
import type React from 'react';
|
||||||
|
import {useCallback, useEffect, useLayoutEffect, useState} from 'react';
|
||||||
|
|
||||||
import type {MarkdownMode} from 'utils/markdown/apply_markdown';
|
import type {MarkdownMode} from 'utils/markdown/apply_markdown';
|
||||||
|
|
||||||
type WideMode = 'wide' | 'normal' | 'narrow' | 'min';
|
type WideMode = 'wide' | 'normal' | 'narrow' | 'min';
|
||||||
|
|
||||||
export function useGetLatest<T>(val: T) {
|
|
||||||
const ref = React.useRef<T>(val);
|
|
||||||
ref.current = val;
|
|
||||||
return React.useCallback(() => ref.current, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
const useResponsiveFormattingBar = (ref: React.RefObject<HTMLDivElement>): WideMode => {
|
const useResponsiveFormattingBar = (ref: React.RefObject<HTMLDivElement>): WideMode => {
|
||||||
const [wideMode, setWideMode] = useState<WideMode>('wide');
|
const [wideMode, setWideMode] = useState<WideMode>('wide');
|
||||||
const handleResize = useCallback(debounce(() => {
|
const handleResize = useCallback(debounce(() => {
|
||||||
|
|||||||
53
webapp/package-lock.json
сгенерированный
53
webapp/package-lock.json
сгенерированный
@@ -52,6 +52,7 @@
|
|||||||
"name": "mattermost-webapp",
|
"name": "mattermost-webapp",
|
||||||
"version": "9.3.0",
|
"version": "9.3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@floating-ui/react": "0.26.6",
|
||||||
"@floating-ui/react-dom": "1.0.0",
|
"@floating-ui/react-dom": "1.0.0",
|
||||||
"@floating-ui/react-dom-interactions": "0.10.3",
|
"@floating-ui/react-dom-interactions": "0.10.3",
|
||||||
"@giphy/js-fetch-api": "5.1.0",
|
"@giphy/js-fetch-api": "5.1.0",
|
||||||
@@ -2362,18 +2363,34 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@floating-ui/core": {
|
"node_modules/@floating-ui/core": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.3",
|
||||||
"license": "MIT",
|
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.3.tgz",
|
||||||
|
"integrity": "sha512-O0WKDOo0yhJuugCx6trZQj5jVJ9yR0ystG2JaNAemYUWce+pmM6WUEFIibnWyEJKdrDxhm75NoSRME35FNaM/Q==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@floating-ui/utils": "^0.1.3"
|
"@floating-ui/utils": "^0.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@floating-ui/dom": {
|
"node_modules/@floating-ui/dom": {
|
||||||
"version": "1.5.3",
|
"version": "1.5.4",
|
||||||
"license": "MIT",
|
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.4.tgz",
|
||||||
|
"integrity": "sha512-jByEsHIY+eEdCjnTVu+E3ephzTOzkQ8hgUfGwos+bg7NlH33Zc5uO+QHz1mrQUOgIKKDD1RtS201P9NvAfq3XQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@floating-ui/core": "^1.4.2",
|
"@floating-ui/core": "^1.5.3",
|
||||||
"@floating-ui/utils": "^0.1.3"
|
"@floating-ui/utils": "^0.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@floating-ui/react": {
|
||||||
|
"version": "0.26.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.6.tgz",
|
||||||
|
"integrity": "sha512-FFDAuSlRwb8CY4/VvYio/wwk/0339B257yRpKwNOjcHWNYL/fgjl1KUvT3K6ZZ4WDbBWYc7Km4ITMuPZrS8omg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@floating-ui/react-dom": "^2.0.6",
|
||||||
|
"@floating-ui/utils": "^0.2.1",
|
||||||
|
"tabbable": "^6.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.0",
|
||||||
|
"react-dom": ">=16.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@floating-ui/react-dom": {
|
"node_modules/@floating-ui/react-dom": {
|
||||||
@@ -2399,9 +2416,22 @@
|
|||||||
"react-dom": ">=16.8.0"
|
"react-dom": ">=16.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@floating-ui/react/node_modules/@floating-ui/react-dom": {
|
||||||
|
"version": "2.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.6.tgz",
|
||||||
|
"integrity": "sha512-IB8aCRFxr8nFkdYZgH+Otd9EVQPJoynxeFRGTB8voPoZMRWo8XjYuCRgpI1btvuKY69XMiLnW+ym7zoBHM90Rw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@floating-ui/dom": "^1.5.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.0",
|
||||||
|
"react-dom": ">=16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@floating-ui/utils": {
|
"node_modules/@floating-ui/utils": {
|
||||||
"version": "0.1.6",
|
"version": "0.2.1",
|
||||||
"license": "MIT"
|
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz",
|
||||||
|
"integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q=="
|
||||||
},
|
},
|
||||||
"node_modules/@formatjs/ecma402-abstract": {
|
"node_modules/@formatjs/ecma402-abstract": {
|
||||||
"version": "1.15.0",
|
"version": "1.15.0",
|
||||||
@@ -22320,6 +22350,11 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/tabbable": {
|
||||||
|
"version": "6.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
|
||||||
|
"integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="
|
||||||
|
},
|
||||||
"node_modules/table": {
|
"node_modules/table": {
|
||||||
"version": "6.8.1",
|
"version": "6.8.1",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import MuiFocusTrap, {FocusTrapProps as MuiFocusTrapProps} from '@mui/base/FocusTrap';
|
|
||||||
|
|
||||||
export interface Props {
|
|
||||||
active: MuiFocusTrapProps['open'];
|
|
||||||
children: MuiFocusTrapProps['children'];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const FocusTrap = ({active, children}: Props) => {
|
|
||||||
return (
|
|
||||||
<MuiFocusTrap open={active}>
|
|
||||||
{children}
|
|
||||||
</MuiFocusTrap>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -4,7 +4,6 @@
|
|||||||
// type
|
// type
|
||||||
export type {Props as GenericModalProps} from './generic_modal/generic_modal';
|
export type {Props as GenericModalProps} from './generic_modal/generic_modal';
|
||||||
export type {CircleSkeletonLoaderProps, RectangleSkeletonLoaderProps} from './skeleton_loader';
|
export type {CircleSkeletonLoaderProps, RectangleSkeletonLoaderProps} from './skeleton_loader';
|
||||||
export type {Props as FocusTrapProps} from './focus_trap';
|
|
||||||
export type {Props as PunchOutCoordsHeightAndWidth} from './common/hooks/useMeasurePunchouts';
|
export type {Props as PunchOutCoordsHeightAndWidth} from './common/hooks/useMeasurePunchouts';
|
||||||
|
|
||||||
// components
|
// components
|
||||||
@@ -14,7 +13,6 @@ export {CircleSkeletonLoader, RectangleSkeletonLoader} from './skeleton_loader';
|
|||||||
export {TourTip} from './tour_tip/tour_tip';
|
export {TourTip} from './tour_tip/tour_tip';
|
||||||
export {TourTipBackdrop} from './tour_tip/tour_tip_backdrop';
|
export {TourTipBackdrop} from './tour_tip/tour_tip_backdrop';
|
||||||
export {PulsatingDot} from './pulsating_dot';
|
export {PulsatingDot} from './pulsating_dot';
|
||||||
export {FocusTrap} from './focus_trap';
|
|
||||||
|
|
||||||
// hooks
|
// hooks
|
||||||
export {useMeasurePunchouts} from './common/hooks/useMeasurePunchouts';
|
export {useMeasurePunchouts} from './common/hooks/useMeasurePunchouts';
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user