@lumx/react 3.6.3-alpha.2 → 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 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, HTMLSpanElement>;
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, useRef, useMemo, useCallback, Children, isValidElement, cloneElement, useLayoutEffect, Fragment as Fragment$1, createContext, useContext, useReducer } from 'react';
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 = true,
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 clickAwayRef = useRef(null);
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
- * Track whether the focus is currently set in the
6474
- * popover.
6475
- * */
6476
- const isFocusedWithin = useRef(false);
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 && (contentRef === null || contentRef === void 0 ? void 0 : contentRef.current), focusElement === null || focusElement === void 0 ? void 0 : focusElement.current);
6520
- const clickAwayRefs = useRef([clickAwayRef, anchorRef]);
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: mergeRefs(setPopperElement, ref, clickAwayRef, contentRef),
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 && handleClose,
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("span", _extends({
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', open],
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