@lumx/react 3.6.3 → 3.6.4
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/index.d.ts +1 -1
- package/index.js +114 -82
- package/index.js.map +1 -1
- package/package.json +3 -3
- package/src/components/expansion-panel/ExpansionPanel.test.tsx +5 -0
- package/src/components/input-helper/InputHelper.tsx +3 -3
- package/src/components/popover/Popover.tsx +15 -55
- package/src/components/popover/useRestoreFocusOnClose.tsx +33 -0
- package/src/components/select/Select.test.tsx +4 -0
- package/src/components/text-field/TextField.test.tsx +5 -0
- package/src/components/tooltip/Tooltip.stories.tsx +16 -0
- package/src/components/tooltip/Tooltip.test.tsx +24 -1
- package/src/components/tooltip/useTooltipOpen.tsx +11 -2
- package/src/utils/isFocusVisible.ts +3 -0
- package/src/utils/mergeRefs.ts +12 -1
- package/src/utils/useBeforeUnmountSentinel.tsx +17 -0
package/index.d.ts
CHANGED
|
@@ -1403,7 +1403,7 @@ interface InputHelperProps extends GenericProps, HasTheme {
|
|
|
1403
1403
|
* @param ref Component ref.
|
|
1404
1404
|
* @return React element.
|
|
1405
1405
|
*/
|
|
1406
|
-
declare const InputHelper: Comp<InputHelperProps,
|
|
1406
|
+
declare const InputHelper: Comp<InputHelperProps, HTMLParagraphElement>;
|
|
1407
1407
|
|
|
1408
1408
|
/**
|
|
1409
1409
|
* Defines the props of the component.
|
package/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { forwardRef, useState, useEffect,
|
|
1
|
+
import React, { forwardRef, useState, useEffect, useMemo, useRef, useCallback, Children, isValidElement, cloneElement, useLayoutEffect, Fragment as Fragment$1, createContext, useContext, useReducer } from 'react';
|
|
2
2
|
import kebabCase from 'lodash/kebabCase';
|
|
3
3
|
import isBoolean from 'lodash/isBoolean';
|
|
4
4
|
import isEmpty from 'lodash/isEmpty';
|
|
@@ -828,6 +828,18 @@ function mergeRefs() {
|
|
|
828
828
|
});
|
|
829
829
|
}
|
|
830
830
|
|
|
831
|
+
/**
|
|
832
|
+
* Same as `mergeRefs` but memoized
|
|
833
|
+
*/
|
|
834
|
+
const useMergeRefs = function () {
|
|
835
|
+
for (var _len2 = arguments.length, refs = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
|
|
836
|
+
refs[_key2] = arguments[_key2];
|
|
837
|
+
}
|
|
838
|
+
return useMemo(() => mergeRefs(...refs),
|
|
839
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
840
|
+
refs);
|
|
841
|
+
};
|
|
842
|
+
|
|
831
843
|
const _excluded$2 = ["anchorToInput", "children", "chips", "className", "closeOnClick", "closeOnClickAway", "closeOnEscape", "disabled", "error", "fitToAnchorWidth", "hasError", "helper", "icon", "inputRef", "clearButtonProps", "isDisabled", "isRequired", "isOpen", "isValid", "label", "name", "offset", "onBlur", "onChange", "onClose", "onFocus", "onInfiniteScroll", "placeholder", "placement", "shouldFocusOnClose", "theme", "value", "textFieldProps"];
|
|
832
844
|
|
|
833
845
|
/**
|
|
@@ -4033,31 +4045,6 @@ const List = Object.assign(InternalList, {
|
|
|
4033
4045
|
useKeyboardListNavigation
|
|
4034
4046
|
});
|
|
4035
4047
|
|
|
4036
|
-
/**
|
|
4037
|
-
* Hook that allows to control when there is a focus event within a given element, meaning
|
|
4038
|
-
* that any element within the given target will trigger the focus in and focus out events.
|
|
4039
|
-
* @param options - UseFocusWithinOptions
|
|
4040
|
-
*/
|
|
4041
|
-
const useFocusWithin = _ref => {
|
|
4042
|
-
let {
|
|
4043
|
-
element,
|
|
4044
|
-
onFocusIn,
|
|
4045
|
-
onFocusOut
|
|
4046
|
-
} = _ref;
|
|
4047
|
-
useEffect(() => {
|
|
4048
|
-
if (element) {
|
|
4049
|
-
element.addEventListener('focusin', onFocusIn);
|
|
4050
|
-
element.addEventListener('focusout', onFocusOut);
|
|
4051
|
-
}
|
|
4052
|
-
return () => {
|
|
4053
|
-
if (element) {
|
|
4054
|
-
element.removeEventListener('focusin', onFocusIn);
|
|
4055
|
-
element.removeEventListener('focusout', onFocusOut);
|
|
4056
|
-
}
|
|
4057
|
-
};
|
|
4058
|
-
}, [onFocusIn, element, onFocusOut]);
|
|
4059
|
-
};
|
|
4060
|
-
|
|
4061
4048
|
/**
|
|
4062
4049
|
* HOC component wrapping a component to skip render if predicate return falsy
|
|
4063
4050
|
*/
|
|
@@ -4075,6 +4062,54 @@ const skipRender = (predicate, Component) => {
|
|
|
4075
4062
|
return Wrapper;
|
|
4076
4063
|
};
|
|
4077
4064
|
|
|
4065
|
+
/** Small helper component using useLayoutEffect to trigger a callback on before unmount. */
|
|
4066
|
+
const OnUnmount = _ref => {
|
|
4067
|
+
let {
|
|
4068
|
+
onBeforeUnmount
|
|
4069
|
+
} = _ref;
|
|
4070
|
+
useLayoutEffect(() => onBeforeUnmount, [onBeforeUnmount]);
|
|
4071
|
+
return null;
|
|
4072
|
+
};
|
|
4073
|
+
|
|
4074
|
+
/**
|
|
4075
|
+
* Provides a sentinel to inject the React tree that triggers the callback on before unmount.
|
|
4076
|
+
*/
|
|
4077
|
+
function useBeforeUnmountSentinel(onBeforeUnmount) {
|
|
4078
|
+
return React.useMemo(() => {
|
|
4079
|
+
if (!onBeforeUnmount) return undefined;
|
|
4080
|
+
return /*#__PURE__*/React.createElement(OnUnmount, {
|
|
4081
|
+
onBeforeUnmount: onBeforeUnmount
|
|
4082
|
+
});
|
|
4083
|
+
}, [onBeforeUnmount]);
|
|
4084
|
+
}
|
|
4085
|
+
|
|
4086
|
+
/**
|
|
4087
|
+
* Provides an unmount sentinel to inject in the popover to detect when it closes and restore the focus to the
|
|
4088
|
+
* anchor if needed.
|
|
4089
|
+
*/
|
|
4090
|
+
function useRestoreFocusOnClose(props, popoverElement) {
|
|
4091
|
+
const onBeforeUnmount = React.useMemo(() => {
|
|
4092
|
+
if (!popoverElement || !props.focusAnchorOnClose) return undefined;
|
|
4093
|
+
return () => {
|
|
4094
|
+
var _props$parentElement;
|
|
4095
|
+
const isFocusWithin = popoverElement === null || popoverElement === void 0 ? void 0 : popoverElement.contains(document.activeElement);
|
|
4096
|
+
if (!isFocusWithin) return;
|
|
4097
|
+
const anchor = props.anchorRef.current;
|
|
4098
|
+
const elementToFocus =
|
|
4099
|
+
// Provided parent element
|
|
4100
|
+
((_props$parentElement = props.parentElement) === null || _props$parentElement === void 0 ? void 0 : _props$parentElement.current) || (
|
|
4101
|
+
// Or first focusable element in anchor
|
|
4102
|
+
anchor ? getFirstAndLastFocusable(anchor).first : undefined) ||
|
|
4103
|
+
// Fallback to anchor
|
|
4104
|
+
anchor;
|
|
4105
|
+
elementToFocus === null || elementToFocus === void 0 ? void 0 : elementToFocus.focus({
|
|
4106
|
+
preventScroll: true
|
|
4107
|
+
});
|
|
4108
|
+
};
|
|
4109
|
+
}, [popoverElement, props.anchorRef, props.focusAnchorOnClose, props.parentElement]);
|
|
4110
|
+
return useBeforeUnmountSentinel(onBeforeUnmount);
|
|
4111
|
+
}
|
|
4112
|
+
|
|
4078
4113
|
/**
|
|
4079
4114
|
* Simple ponyfill for Object.fromEntries
|
|
4080
4115
|
*/
|
|
@@ -6409,6 +6444,7 @@ const CLASSNAME$i = getRootClassName(COMPONENT_NAME$l);
|
|
|
6409
6444
|
const DEFAULT_PROPS$g = {
|
|
6410
6445
|
elevation: 3,
|
|
6411
6446
|
placement: Placement.AUTO,
|
|
6447
|
+
focusAnchorOnClose: true,
|
|
6412
6448
|
usePortal: true,
|
|
6413
6449
|
zIndex: 9999
|
|
6414
6450
|
};
|
|
@@ -6434,7 +6470,7 @@ const _InnerPopover = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
6434
6470
|
onClose,
|
|
6435
6471
|
parentElement,
|
|
6436
6472
|
usePortal,
|
|
6437
|
-
focusAnchorOnClose
|
|
6473
|
+
focusAnchorOnClose,
|
|
6438
6474
|
withFocusTrap,
|
|
6439
6475
|
boundaryRef,
|
|
6440
6476
|
fitToAnchorWidth,
|
|
@@ -6446,8 +6482,7 @@ const _InnerPopover = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
6446
6482
|
zIndex
|
|
6447
6483
|
} = props,
|
|
6448
6484
|
forwardedProps = _objectWithoutProperties(props, _excluded$m);
|
|
6449
|
-
const
|
|
6450
|
-
const contentRef = useRef(null);
|
|
6485
|
+
const popoverRef = useRef(null);
|
|
6451
6486
|
const {
|
|
6452
6487
|
styles,
|
|
6453
6488
|
attributes,
|
|
@@ -6468,58 +6503,20 @@ const _InnerPopover = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
6468
6503
|
style,
|
|
6469
6504
|
zIndex
|
|
6470
6505
|
});
|
|
6471
|
-
|
|
6472
|
-
|
|
6473
|
-
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
|
|
6477
|
-
useFocusWithin({
|
|
6478
|
-
element: popperElement || null,
|
|
6479
|
-
onFocusIn: () => {
|
|
6480
|
-
isFocusedWithin.current = true;
|
|
6481
|
-
},
|
|
6482
|
-
onFocusOut: () => {
|
|
6483
|
-
isFocusedWithin.current = false;
|
|
6484
|
-
}
|
|
6485
|
-
});
|
|
6486
|
-
|
|
6487
|
-
/** Action on close */
|
|
6488
|
-
const handleClose = useCallback(() => {
|
|
6489
|
-
if (!onClose) {
|
|
6490
|
-
return;
|
|
6491
|
-
}
|
|
6492
|
-
|
|
6493
|
-
/**
|
|
6494
|
-
* If the focus is currently within the popover
|
|
6495
|
-
* when the popover closes, reset the focus back to the anchor element
|
|
6496
|
-
* unless specifically requested not to.
|
|
6497
|
-
*/
|
|
6498
|
-
if (isFocusedWithin.current && focusAnchorOnClose) {
|
|
6499
|
-
var _elementToFocus;
|
|
6500
|
-
let elementToFocus = parentElement === null || parentElement === void 0 ? void 0 : parentElement.current;
|
|
6501
|
-
if (!elementToFocus && anchorRef !== null && anchorRef !== void 0 && anchorRef.current) {
|
|
6502
|
-
// Focus the first focusable element in anchor.
|
|
6503
|
-
elementToFocus = getFirstAndLastFocusable(anchorRef.current).first;
|
|
6504
|
-
}
|
|
6505
|
-
if (!elementToFocus) {
|
|
6506
|
-
// Fallback on the anchor element.
|
|
6507
|
-
elementToFocus = anchorRef === null || anchorRef === void 0 ? void 0 : anchorRef.current;
|
|
6508
|
-
}
|
|
6509
|
-
(_elementToFocus = elementToFocus) === null || _elementToFocus === void 0 ? void 0 : _elementToFocus.focus({
|
|
6510
|
-
preventScroll: true
|
|
6511
|
-
});
|
|
6512
|
-
}
|
|
6513
|
-
onClose();
|
|
6514
|
-
}, [anchorRef, focusAnchorOnClose, onClose, parentElement]);
|
|
6515
|
-
useCallbackOnEscape(handleClose, isOpen && closeOnEscape);
|
|
6506
|
+
const unmountSentinel = useRestoreFocusOnClose({
|
|
6507
|
+
focusAnchorOnClose,
|
|
6508
|
+
anchorRef,
|
|
6509
|
+
parentElement
|
|
6510
|
+
}, popperElement);
|
|
6511
|
+
useCallbackOnEscape(onClose, isOpen && closeOnEscape);
|
|
6516
6512
|
|
|
6517
6513
|
/** Only set focus within if the focus trap is disabled as they interfere with one another. */
|
|
6518
6514
|
useFocus(focusElement === null || focusElement === void 0 ? void 0 : focusElement.current, !withFocusTrap && isOpen && isPositioned);
|
|
6519
|
-
useFocusTrap(withFocusTrap && isOpen && (
|
|
6520
|
-
const clickAwayRefs = useRef([
|
|
6515
|
+
useFocusTrap(withFocusTrap && isOpen && (popoverRef === null || popoverRef === void 0 ? void 0 : popoverRef.current), focusElement === null || focusElement === void 0 ? void 0 : focusElement.current);
|
|
6516
|
+
const clickAwayRefs = useRef([popoverRef, anchorRef]);
|
|
6517
|
+
const mergedRefs = useMergeRefs(setPopperElement, ref, popoverRef);
|
|
6521
6518
|
return isOpen ? renderPopover( /*#__PURE__*/React.createElement(Component, _extends({}, forwardedProps, {
|
|
6522
|
-
ref:
|
|
6519
|
+
ref: mergedRefs,
|
|
6523
6520
|
className: classnames(className, handleBasicClasses({
|
|
6524
6521
|
prefix: CLASSNAME$i,
|
|
6525
6522
|
theme,
|
|
@@ -6527,8 +6524,8 @@ const _InnerPopover = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
6527
6524
|
position
|
|
6528
6525
|
})),
|
|
6529
6526
|
style: styles.popover
|
|
6530
|
-
}, attributes.popper), /*#__PURE__*/React.createElement(ClickAwayProvider, {
|
|
6531
|
-
callback: closeOnClickAway &&
|
|
6527
|
+
}, attributes.popper), unmountSentinel, /*#__PURE__*/React.createElement(ClickAwayProvider, {
|
|
6528
|
+
callback: closeOnClickAway && onClose,
|
|
6532
6529
|
childrenRefs: clickAwayRefs
|
|
6533
6530
|
}, hasArrow && /*#__PURE__*/React.createElement("div", {
|
|
6534
6531
|
ref: setArrowElement,
|
|
@@ -7699,7 +7696,7 @@ const InputHelper = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
7699
7696
|
const {
|
|
7700
7697
|
color
|
|
7701
7698
|
} = INPUT_HELPER_CONFIGURATION[kind] || {};
|
|
7702
|
-
return /*#__PURE__*/React.createElement("
|
|
7699
|
+
return /*#__PURE__*/React.createElement("p", _extends({
|
|
7703
7700
|
ref: ref
|
|
7704
7701
|
}, forwardedProps, {
|
|
7705
7702
|
className: classnames(className, handleBasicClasses({
|
|
@@ -10879,6 +10876,31 @@ const useSlideshowControls = _ref => {
|
|
|
10879
10876
|
};
|
|
10880
10877
|
};
|
|
10881
10878
|
|
|
10879
|
+
/**
|
|
10880
|
+
* Hook that allows to control when there is a focus event within a given element, meaning
|
|
10881
|
+
* that any element within the given target will trigger the focus in and focus out events.
|
|
10882
|
+
* @param options - UseFocusWithinOptions
|
|
10883
|
+
*/
|
|
10884
|
+
const useFocusWithin = _ref => {
|
|
10885
|
+
let {
|
|
10886
|
+
element,
|
|
10887
|
+
onFocusIn,
|
|
10888
|
+
onFocusOut
|
|
10889
|
+
} = _ref;
|
|
10890
|
+
useEffect(() => {
|
|
10891
|
+
if (element) {
|
|
10892
|
+
element.addEventListener('focusin', onFocusIn);
|
|
10893
|
+
element.addEventListener('focusout', onFocusOut);
|
|
10894
|
+
}
|
|
10895
|
+
return () => {
|
|
10896
|
+
if (element) {
|
|
10897
|
+
element.removeEventListener('focusin', onFocusIn);
|
|
10898
|
+
element.removeEventListener('focusout', onFocusOut);
|
|
10899
|
+
}
|
|
10900
|
+
};
|
|
10901
|
+
}, [onFocusIn, element, onFocusOut]);
|
|
10902
|
+
};
|
|
10903
|
+
|
|
10882
10904
|
/**
|
|
10883
10905
|
* Classname set on elements whose focus was blocked.
|
|
10884
10906
|
* This is to easily find elements that have been tempered with,
|
|
@@ -12867,6 +12889,12 @@ const browserDoesNotSupportHover = () => {
|
|
|
12867
12889
|
return !!((_window$matchMedia = (_window = window).matchMedia) !== null && _window$matchMedia !== void 0 && _window$matchMedia.call(_window, '(hover: none)').matches);
|
|
12868
12890
|
};
|
|
12869
12891
|
|
|
12892
|
+
/** Check if the focus is visible on the given element */
|
|
12893
|
+
const isFocusVisible = element => {
|
|
12894
|
+
var _element$matches;
|
|
12895
|
+
return element === null || element === void 0 ? void 0 : (_element$matches = element.matches) === null || _element$matches === void 0 ? void 0 : _element$matches.call(element, ':focus-visible, [data-focus-visible-added]');
|
|
12896
|
+
};
|
|
12897
|
+
|
|
12870
12898
|
/**
|
|
12871
12899
|
* Hook controlling tooltip visibility using mouse hover the anchor and delay.
|
|
12872
12900
|
*
|
|
@@ -12959,8 +12987,12 @@ function useTooltipOpen(delay, anchorElement) {
|
|
|
12959
12987
|
|
|
12960
12988
|
// Events always applied no matter the browser:.
|
|
12961
12989
|
events.push(
|
|
12962
|
-
// Open on focus.
|
|
12963
|
-
[anchorElement, 'focusin',
|
|
12990
|
+
// Open on focus (only if focus is visible).
|
|
12991
|
+
[anchorElement, 'focusin', e => {
|
|
12992
|
+
// Skip if focus is not visible
|
|
12993
|
+
if (!isFocusVisible(e.target)) return;
|
|
12994
|
+
open();
|
|
12995
|
+
}],
|
|
12964
12996
|
// Close on lost focus.
|
|
12965
12997
|
[anchorElement, 'focusout', closeImmediately]);
|
|
12966
12998
|
|