Refactor panel with withFocusOutside (https://github.com/woocommerce/woocommerce-admin/pull/6233)
* Refactor panel with withFocusOutside * Remove react-click-outside dependency * Handle PR feedback * Handle PR feedback-2
This commit is contained in:
parent
452494a4fe
commit
1fc78d93c9
|
@ -2,8 +2,7 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import clickOutside from 'react-click-outside';
|
||||
import { Component, lazy, Suspense } from '@wordpress/element';
|
||||
import { Component, lazy } from '@wordpress/element';
|
||||
import { Button } from '@wordpress/components';
|
||||
import { compose } from '@wordpress/compose';
|
||||
import { withDispatch, withSelect } from '@wordpress/data';
|
||||
|
@ -12,7 +11,7 @@ import CrossIcon from 'gridicons/dist/cross-small';
|
|||
import classnames from 'classnames';
|
||||
import { Icon, help as helpIcon } from '@wordpress/icons';
|
||||
import { getAdminLink } from '@woocommerce/wc-admin-settings';
|
||||
import { H, Section, Spinner } from '@woocommerce/components';
|
||||
import { H, Section } from '@woocommerce/components';
|
||||
import { OPTIONS_STORE_NAME } from '@woocommerce/data';
|
||||
import { getHistory, getNewPath } from '@woocommerce/navigation';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
|
@ -28,6 +27,7 @@ import { Tabs } from './tabs';
|
|||
import { SetupProgress } from './setup-progress';
|
||||
import { DisplayOptions } from './display-options';
|
||||
import { HighlightTooltip } from './highlight-tooltip';
|
||||
import { Panel } from './panel';
|
||||
|
||||
const HelpPanel = lazy( () =>
|
||||
import( /* webpackChunkName: "activity-panels-help" */ './panels/help' )
|
||||
|
@ -133,6 +133,7 @@ export class ActivityPanel extends Component {
|
|||
isEmbedded,
|
||||
setupTaskListComplete,
|
||||
setupTaskListHidden,
|
||||
updateOptions,
|
||||
} = this.props;
|
||||
|
||||
const isPerformingSetupTask = this.isPerformingSetupTask();
|
||||
|
@ -167,6 +168,27 @@ export class ActivityPanel extends Component {
|
|||
name: 'setup',
|
||||
title: __( 'Store Setup', 'woocommerce-admin' ),
|
||||
icon: <SetupProgress />,
|
||||
onClick: () => {
|
||||
const currentLocation = window.location.href;
|
||||
const homescreenLocation = getAdminLink(
|
||||
'admin.php?page=wc-admin'
|
||||
);
|
||||
|
||||
// Don't navigate if we're already on the homescreen, this will cause an infinite loop
|
||||
if ( currentLocation !== homescreenLocation ) {
|
||||
// Ensure that if the user is trying to get to the task list they can see it even if
|
||||
// it was dismissed.
|
||||
if ( setupTaskListHidden === 'no' ) {
|
||||
this.redirectToHomeScreen();
|
||||
} else {
|
||||
updateOptions( {
|
||||
woocommerce_task_list_hidden: 'no',
|
||||
} ).then( this.redirectToHomeScreen );
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
}
|
||||
: null;
|
||||
|
||||
|
@ -201,77 +223,6 @@ export class ActivityPanel extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
renderPanel() {
|
||||
const { updateOptions, setupTaskListHidden } = this.props;
|
||||
const { isPanelOpen, currentTab, isPanelSwitching } = this.state;
|
||||
const tab = find( this.getTabs(), { name: currentTab } );
|
||||
|
||||
if ( ! tab ) {
|
||||
return (
|
||||
<div className="woocommerce-layout__activity-panel-wrapper" />
|
||||
);
|
||||
}
|
||||
|
||||
const clearPanel = () => {
|
||||
this.clearPanel();
|
||||
};
|
||||
|
||||
if ( currentTab === 'display-options' ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( currentTab === 'setup' ) {
|
||||
const currentLocation = window.location.href;
|
||||
const homescreenLocation = getAdminLink(
|
||||
'admin.php?page=wc-admin'
|
||||
);
|
||||
|
||||
// Don't navigate if we're already on the homescreen, this will cause an infinite loop
|
||||
if ( currentLocation !== homescreenLocation ) {
|
||||
// Ensure that if the user is trying to get to the task list they can see it even if
|
||||
// it was dismissed.
|
||||
if ( setupTaskListHidden === 'no' ) {
|
||||
this.redirectToHomeScreen();
|
||||
} else {
|
||||
updateOptions( {
|
||||
woocommerce_task_list_hidden: 'no',
|
||||
} ).then( this.redirectToHomeScreen );
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const classNames = classnames(
|
||||
'woocommerce-layout__activity-panel-wrapper',
|
||||
{
|
||||
'is-open': isPanelOpen,
|
||||
'is-switching': isPanelSwitching,
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={ classNames }
|
||||
tabIndex={ 0 }
|
||||
role="tabpanel"
|
||||
aria-label={ tab.title }
|
||||
onTransitionEnd={ clearPanel }
|
||||
onAnimationEnd={ clearPanel }
|
||||
>
|
||||
<div
|
||||
className="woocommerce-layout__activity-panel-content"
|
||||
key={ 'activity-panel-' + currentTab }
|
||||
id={ 'activity-panel-' + currentTab }
|
||||
>
|
||||
<Suspense fallback={ <Spinner /> }>
|
||||
{ this.getPanelContent( currentTab ) }
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
redirectToHomeScreen() {
|
||||
if ( isWCAdmin( window.location.href ) ) {
|
||||
getHistory().push( getNewPath( {}, '/', {} ) );
|
||||
|
@ -373,10 +324,22 @@ export class ActivityPanel extends Component {
|
|||
tabOpen={ isPanelOpen }
|
||||
selectedTab={ currentTab }
|
||||
onTabClick={ ( tab, tabOpen ) => {
|
||||
if ( tab.onClick ) {
|
||||
tab.onClick();
|
||||
return;
|
||||
}
|
||||
this.togglePanel( tab, tabOpen );
|
||||
} }
|
||||
/>
|
||||
{ this.renderPanel() }
|
||||
<Panel
|
||||
isPanelOpen
|
||||
currentTab
|
||||
isPanelSwitching
|
||||
tab={ find( this.getTabs(), { name: currentTab } ) }
|
||||
content={ this.getPanelContent( currentTab ) }
|
||||
closePanel={ () => this.closePanel() }
|
||||
clearPanel={ () => this.clearPanel() }
|
||||
/>
|
||||
</div>
|
||||
</Section>
|
||||
{ showHelpHighlightTooltip ? (
|
||||
|
@ -431,6 +394,5 @@ export default compose(
|
|||
} ),
|
||||
withDispatch( ( dispatch ) => ( {
|
||||
updateOptions: dispatch( OPTIONS_STORE_NAME ).updateOptions,
|
||||
} ) ),
|
||||
clickOutside
|
||||
} ) )
|
||||
)( ActivityPanel );
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Suspense } from '@wordpress/element';
|
||||
import classnames from 'classnames';
|
||||
import { Spinner } from '@woocommerce/components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import useFocusOnMount from '../../hooks/useFocusOnMount';
|
||||
import useFocusOutside from '../../hooks/useFocusOutside';
|
||||
|
||||
export const Panel = ( {
|
||||
content,
|
||||
isPanelOpen,
|
||||
currentTab,
|
||||
isPanelSwitching,
|
||||
tab,
|
||||
closePanel,
|
||||
clearPanel,
|
||||
} ) => {
|
||||
const handleFocusOutside = ( event ) => {
|
||||
const isClickOnModalOrSnackbar =
|
||||
event.target.closest(
|
||||
'.woocommerce-inbox-dismiss-confirmation_modal'
|
||||
) || event.target.closest( '.components-snackbar__action' );
|
||||
|
||||
if ( isPanelOpen && ! isClickOnModalOrSnackbar ) {
|
||||
closePanel();
|
||||
}
|
||||
};
|
||||
|
||||
const ref = useFocusOnMount();
|
||||
const useFocusOutsideProps = useFocusOutside( handleFocusOutside );
|
||||
|
||||
if ( ! tab ) {
|
||||
return <div className="woocommerce-layout__activity-panel-wrapper" />;
|
||||
}
|
||||
|
||||
if ( ! content ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const classNames = classnames(
|
||||
'woocommerce-layout__activity-panel-wrapper',
|
||||
{
|
||||
'is-open': isPanelOpen,
|
||||
'is-switching': isPanelSwitching,
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={ classNames }
|
||||
tabIndex={ 0 }
|
||||
role="tabpanel"
|
||||
aria-label={ tab.title }
|
||||
onTransitionEnd={ clearPanel }
|
||||
onAnimationEnd={ clearPanel }
|
||||
{ ...useFocusOutsideProps }
|
||||
ref={ ref }
|
||||
>
|
||||
<div
|
||||
className="woocommerce-layout__activity-panel-content"
|
||||
key={ 'activity-panel-' + currentTab }
|
||||
id={ 'activity-panel-' + currentTab }
|
||||
>
|
||||
<Suspense fallback={ <Spinner /> }>{ content }</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Panel;
|
|
@ -0,0 +1,62 @@
|
|||
/**
|
||||
* This hook was directly copied from https://github.com/WordPress/gutenberg/blob/master/packages/compose/src/hooks/use-focus-on-mount/index.js
|
||||
* to avoid its absence in older versions of WordPress.
|
||||
*
|
||||
* This can be removed once the minimum supported version of WordPress includes this hook.
|
||||
*/
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useRef, useEffect, useCallback } from '@wordpress/element';
|
||||
import { focus } from '@wordpress/dom';
|
||||
|
||||
/**
|
||||
* Hook used to focus the first tabbable element on mount.
|
||||
*
|
||||
* @param {boolean|string} focusOnMount Focus on mount mode.
|
||||
* @return {Function} Ref callback.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* import { useFocusOnMount } from '@wordpress/compose';
|
||||
*
|
||||
* const WithFocusOnMount = () => {
|
||||
* const ref = useFocusOnMount()
|
||||
* return (
|
||||
* <div ref={ ref }>
|
||||
* <Button />
|
||||
* <Button />
|
||||
* </div>
|
||||
* );
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export default function useFocusOnMount( focusOnMount = 'firstElement' ) {
|
||||
const focusOnMountRef = useRef( focusOnMount );
|
||||
useEffect( () => {
|
||||
focusOnMountRef.current = focusOnMount;
|
||||
}, [ focusOnMount ] );
|
||||
|
||||
return useCallback( ( node ) => {
|
||||
if ( ! node || focusOnMountRef.current === false ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( node.contains( node.ownerDocument.activeElement ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
let target = node;
|
||||
|
||||
if ( focusOnMountRef.current === 'firstElement' ) {
|
||||
const firstTabbable = focus.tabbable.find( node )[ 0 ];
|
||||
|
||||
if ( firstTabbable ) {
|
||||
target = firstTabbable;
|
||||
}
|
||||
}
|
||||
|
||||
target.focus();
|
||||
}, [] );
|
||||
}
|
|
@ -0,0 +1,187 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { includes } from 'lodash';
|
||||
import { useCallback, useEffect, useRef } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Input types which are classified as button types, for use in considering
|
||||
* whether element is a (focus-normalized) button.
|
||||
*
|
||||
* @type {string[]}
|
||||
*/
|
||||
const INPUT_BUTTON_TYPES = [ 'button', 'submit' ];
|
||||
|
||||
/**
|
||||
* @typedef {HTMLButtonElement | HTMLLinkElement | HTMLInputElement} FocusNormalizedButton
|
||||
*/
|
||||
|
||||
// Disable reason: Rule doesn't support predicate return types
|
||||
/* eslint-disable jsdoc/valid-types */
|
||||
/**
|
||||
* Returns true if the given element is a button element subject to focus
|
||||
* normalization, or false otherwise.
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus
|
||||
*
|
||||
* @param {EventTarget} eventTarget The target from a mouse or touch event.
|
||||
*
|
||||
* @return {eventTarget is FocusNormalizedButton} Whether element is a button.
|
||||
*/
|
||||
function isFocusNormalizedButton( eventTarget ) {
|
||||
if ( ! ( eventTarget instanceof window.HTMLElement ) ) {
|
||||
return false;
|
||||
}
|
||||
switch ( eventTarget.nodeName ) {
|
||||
case 'A':
|
||||
case 'BUTTON':
|
||||
return true;
|
||||
|
||||
case 'INPUT':
|
||||
return includes(
|
||||
INPUT_BUTTON_TYPES,
|
||||
/** @type {HTMLInputElement} */ ( eventTarget ).type
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
/* eslint-enable jsdoc/valid-types */
|
||||
|
||||
/**
|
||||
* @typedef {import('react').SyntheticEvent} SyntheticEvent
|
||||
*/
|
||||
|
||||
/**
|
||||
* @callback EventCallback
|
||||
* @param {SyntheticEvent} event input related event.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef FocusOutsideReactElement
|
||||
* @property {EventCallback} handleFocusOutside callback for a focus outside event.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('react').MutableRefObject<FocusOutsideReactElement | undefined>} FocusOutsideRef
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} FocusOutsideReturnValue
|
||||
* @property {EventCallback} onFocus An event handler for focus events.
|
||||
* @property {EventCallback} onBlur An event handler for blur events.
|
||||
* @property {EventCallback} onMouseDown An event handler for mouse down events.
|
||||
* @property {EventCallback} onMouseUp An event handler for mouse up events.
|
||||
* @property {EventCallback} onTouchStart An event handler for touch start events.
|
||||
* @property {EventCallback} onTouchEnd An event handler for touch end events.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A react hook that can be used to check whether focus has moved outside the
|
||||
* element the event handlers are bound to.
|
||||
*
|
||||
* @param {EventCallback} onFocusOutside A callback triggered when focus moves outside
|
||||
* the element the event handlers are bound to.
|
||||
*
|
||||
* @return {FocusOutsideReturnValue} An object containing event handlers. Bind the event handlers
|
||||
* to a wrapping element element to capture when focus moves
|
||||
* outside that element.
|
||||
*/
|
||||
export default function useFocusOutside( onFocusOutside ) {
|
||||
const currentOnFocusOutside = useRef( onFocusOutside );
|
||||
useEffect( () => {
|
||||
currentOnFocusOutside.current = onFocusOutside;
|
||||
}, [ onFocusOutside ] );
|
||||
|
||||
const preventBlurCheck = useRef( false );
|
||||
|
||||
/**
|
||||
* @type {import('react').MutableRefObject<number | undefined>}
|
||||
*/
|
||||
const blurCheckTimeoutId = useRef();
|
||||
|
||||
/**
|
||||
* Cancel a blur check timeout.
|
||||
*/
|
||||
const cancelBlurCheck = useCallback( () => {
|
||||
clearTimeout( blurCheckTimeoutId.current );
|
||||
}, [] );
|
||||
|
||||
// Cancel blur checks on unmount.
|
||||
useEffect( () => {
|
||||
return () => cancelBlurCheck();
|
||||
}, [] );
|
||||
|
||||
// Cancel a blur check if the callback or ref is no longer provided.
|
||||
useEffect( () => {
|
||||
if ( ! onFocusOutside ) {
|
||||
cancelBlurCheck();
|
||||
}
|
||||
}, [ onFocusOutside, cancelBlurCheck ] );
|
||||
|
||||
/**
|
||||
* Handles a mousedown or mouseup event to respectively assign and
|
||||
* unassign a flag for preventing blur check on button elements. Some
|
||||
* browsers, namely Firefox and Safari, do not emit a focus event on
|
||||
* button elements when clicked, while others do. The logic here
|
||||
* intends to normalize this as treating click on buttons as focus.
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus
|
||||
*
|
||||
* @param {SyntheticEvent} event Event for mousedown or mouseup.
|
||||
*/
|
||||
const normalizeButtonFocus = useCallback( ( event ) => {
|
||||
const { type, target } = event;
|
||||
const isInteractionEnd = includes( [ 'mouseup', 'touchend' ], type );
|
||||
|
||||
if ( isInteractionEnd ) {
|
||||
preventBlurCheck.current = false;
|
||||
} else if ( isFocusNormalizedButton( target ) ) {
|
||||
preventBlurCheck.current = true;
|
||||
}
|
||||
}, [] );
|
||||
|
||||
/**
|
||||
* A callback triggered when a blur event occurs on the element the handler
|
||||
* is bound to.
|
||||
*
|
||||
* Calls the `onFocusOutside` callback in an immediate timeout if focus has
|
||||
* move outside the bound element and is still within the document.
|
||||
*
|
||||
* @param {SyntheticEvent} event Blur event.
|
||||
*/
|
||||
const queueBlurCheck = useCallback( ( event ) => {
|
||||
// React does not allow using an event reference asynchronously
|
||||
// due to recycling behavior, except when explicitly persisted.
|
||||
event.persist();
|
||||
|
||||
// Skip blur check if clicking button. See `normalizeButtonFocus`.
|
||||
if ( preventBlurCheck.current ) {
|
||||
return;
|
||||
}
|
||||
|
||||
blurCheckTimeoutId.current = setTimeout( () => {
|
||||
// If document is not focused then focus should remain
|
||||
// inside the wrapped component and therefore we cancel
|
||||
// this blur event thereby leaving focus in place.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Document/hasFocus.
|
||||
if ( ! document.hasFocus() ) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
if ( typeof currentOnFocusOutside.current === 'function' ) {
|
||||
currentOnFocusOutside.current( event );
|
||||
}
|
||||
}, 0 );
|
||||
}, [] );
|
||||
|
||||
return {
|
||||
onFocus: cancelBlurCheck,
|
||||
onMouseDown: normalizeButtonFocus,
|
||||
onMouseUp: normalizeButtonFocus,
|
||||
onTouchStart: normalizeButtonFocus,
|
||||
onTouchEnd: normalizeButtonFocus,
|
||||
onBlur: queueBlurCheck,
|
||||
};
|
||||
}
|
|
@ -9065,8 +9065,7 @@
|
|||
"integrity": "sha512-/YFkF0wwYmF0M58wPVrTtFopVwqdsmMAcrgQGnUFIj3JwXQumKR1co99DhDkjJm9vDMYXFTniwKqwvhBxdviSw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/runtime-corejs2": "7.7.4",
|
||||
"locutus": "2.0.11"
|
||||
"@babel/runtime-corejs2": "7.7.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime-corejs2": {
|
||||
|
@ -9075,8 +9074,7 @@
|
|||
"integrity": "sha512-hKNcmHQbBSJFnZ82ewYtWDZ3fXkP/l1XcfRtm7c8gHPM/DMecJtFFBEp7KMLZTuHwwb7RfemHdsEnd7L916Z6A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"core-js": "^2.6.5",
|
||||
"regenerator-runtime": "^0.13.2"
|
||||
"core-js": "^2.6.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10198,11 +10196,7 @@
|
|||
"resolved": "/mnt/renovate/gh/woocommerce/woocommerce-admin/packages/navigation",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/runtime-corejs2": "7.12.5",
|
||||
"@woocommerce/experimental": "file:packages/experimental",
|
||||
"history": "4.10.1",
|
||||
"lodash": "4.17.15",
|
||||
"qs": "6.9.6"
|
||||
"lodash": "4.17.15"
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash": {
|
||||
|
@ -14790,6 +14784,15 @@
|
|||
"integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==",
|
||||
"optional": true
|
||||
},
|
||||
"bindings": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"file-uri-to-path": "1.0.0"
|
||||
}
|
||||
},
|
||||
"bl": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
||||
|
@ -20544,6 +20547,12 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"file-uri-to-path": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
|
||||
"optional": true
|
||||
},
|
||||
"filelist": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.2.tgz",
|
||||
|
@ -25925,6 +25934,7 @@
|
|||
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"bindings": "^1.5.0",
|
||||
"nan": "^2.12.1"
|
||||
}
|
||||
}
|
||||
|
@ -32591,21 +32601,6 @@
|
|||
"object-assign": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"react-click-outside": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-click-outside/-/react-click-outside-3.0.1.tgz",
|
||||
"integrity": "sha512-d0KWFvBt+esoZUF15rL2UBB7jkeAqLU8L/Ny35oLK6fW6mIbOv/ChD+ExF4sR9PD26kVx+9hNfD0FTIqRZEyRQ==",
|
||||
"requires": {
|
||||
"hoist-non-react-statics": "^2.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"hoist-non-react-statics": {
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
|
||||
"integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-color": {
|
||||
"version": "2.19.3",
|
||||
"resolved": "https://registry.npmjs.org/react-color/-/react-color-2.19.3.tgz",
|
||||
|
@ -38224,6 +38219,7 @@
|
|||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"bindings": "^1.5.0",
|
||||
"nan": "^2.12.1"
|
||||
}
|
||||
}
|
||||
|
@ -39349,4 +39345,4 @@
|
|||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -114,9 +114,9 @@
|
|||
"github-label-sync": "2.0.0",
|
||||
"gridicons": "3.3.1",
|
||||
"interpolate-components": "1.1.1",
|
||||
"memize": "^1.1.0",
|
||||
"memoize-one": "5.1.1",
|
||||
"qs": "6.9.6",
|
||||
"react-click-outside": "3.0.1",
|
||||
"react-dates": "17.2.0",
|
||||
"react-router-dom": "5.2.0",
|
||||
"react-transition-group": "4.4.1",
|
||||
|
|
Loading…
Reference in New Issue