@lumx/react 4.4.1-alpha.1 → 4.4.1-alpha.2

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.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Kind as Kind$1, Size as Size$1, ColorPalette as ColorPalette$1, Emphasis as Emphasis$1, ColorVariant, Theme as Theme$1, AspectRatio as AspectRatio$1, DIALOG_TRANSITION_DURATION, Orientation as Orientation$1, Alignment, NOTIFICATION_TRANSITION_DURATION, TOOLTIP_LONG_PRESS_DELAY, TOOLTIP_HOVER_DELAY } from '@lumx/core/js/constants';
1
+ import { Kind as Kind$1, Size as Size$1, ColorPalette as ColorPalette$1, Emphasis as Emphasis$1, ColorVariant, VISUALLY_HIDDEN, Theme as Theme$1, AspectRatio as AspectRatio$1, DOCUMENT, WINDOW, DIALOG_TRANSITION_DURATION, IS_BROWSER as IS_BROWSER$1, Orientation as Orientation$1, Alignment, NOTIFICATION_TRANSITION_DURATION } from '@lumx/core/js/constants';
2
2
  export * from '@lumx/core/js/constants';
3
3
  export * from '@lumx/core/js/types';
4
4
  import * as React from 'react';
@@ -16,7 +16,7 @@ import concat from 'lodash/concat.js';
16
16
  import dropRight from 'lodash/dropRight.js';
17
17
  import partition from 'lodash/partition.js';
18
18
  import reduce from 'lodash/reduce.js';
19
- import { u as useDisabledStateContext, P as Portal, C as ClickAwayProvider } from './_internal/DpdvhbTO.js';
19
+ import { u as useDisabledStateContext, P as Portal, C as ClickAwayProvider } from './_internal/BfgxEBp6.js';
20
20
  import isEmpty from 'lodash/isEmpty.js';
21
21
  import { getDisabledState } from '@lumx/core/js/utils/disabledState';
22
22
  import { mdiCloseCircle } from '@lumx/icons/esm/close-circle.js';
@@ -676,6 +676,12 @@ function typography(typo) {
676
676
  return `lumx-typography-${typo}`;
677
677
  }
678
678
 
679
+ /**
680
+ * Visually hidden class name.
681
+ * Used to hide elements from view but keep them readable from screen readers
682
+ */
683
+ const visuallyHidden = () => VISUALLY_HIDDEN;
684
+
679
685
  /**
680
686
  * Modifier
681
687
  * @example { 'is-disabled': true, 'is-selected': false }
@@ -778,6 +784,29 @@ function bem(baseName) {
778
784
  };
779
785
  }
780
786
 
787
+ /**
788
+ * Animation duration constants. Take into consideration that if you change one of these variables,
789
+ * you need to update their scss counterpart as well
790
+ */
791
+
792
+ /**
793
+ * Delay on hover after which we open or close the tooltip.
794
+ * Only applies to devices supporting pointer hover.
795
+ */
796
+ const TOOLTIP_HOVER_DELAY = {
797
+ open: 500,
798
+ close: 500
799
+ };
800
+
801
+ /**
802
+ * Delay on long press after which we open or close the tooltip.
803
+ * Only applies to devices not supporting pointer hover.
804
+ */
805
+ const TOOLTIP_LONG_PRESS_DELAY = {
806
+ open: 250,
807
+ close: 3000
808
+ };
809
+
781
810
  /**
782
811
  * Alignments.
783
812
  */
@@ -844,6 +873,11 @@ const ColorPalette = {
844
873
  red: 'red',
845
874
  light: 'light'};
846
875
 
876
+ /**
877
+ * Check if we are running in a true browser (not SSR and not jsdom test environment).
878
+ */
879
+ const IS_BROWSER = typeof window !== 'undefined' && !window.navigator.userAgent.includes('jsdom');
880
+
847
881
  /**
848
882
  * Component display name.
849
883
  */
@@ -2985,21 +3019,6 @@ const DatePickerField = forwardRef((props, ref) => {
2985
3019
  });
2986
3020
  DatePickerField.displayName = COMPONENT_NAME$11;
2987
3021
 
2988
- /**
2989
- * Optional global `window` instance (not defined when running SSR).
2990
- */
2991
- const WINDOW = typeof window !== 'undefined' ? window : undefined;
2992
-
2993
- /**
2994
- * Optional global `document` instance (not defined when running SSR).
2995
- */
2996
- const DOCUMENT = typeof document !== 'undefined' ? document : undefined;
2997
-
2998
- /**
2999
- * Check if we are running in a true browser
3000
- */
3001
- const IS_BROWSER = typeof navigator !== 'undefined' && !navigator.userAgent.includes('jsdom');
3002
-
3003
3022
  /**
3004
3023
  * Keep track of listeners, only the last registered listener gets activated at any point (previously registered
3005
3024
  * listener are disabled).
@@ -6804,7 +6823,7 @@ const ExpansionPanel = forwardRef((props, ref) => {
6804
6823
  React__default.useEffect(() => {
6805
6824
  if (isOpen || closeMode === 'hide') {
6806
6825
  setChildrenVisible(true);
6807
- } else if (!IS_BROWSER) {
6826
+ } else if (!IS_BROWSER$1) {
6808
6827
  // Outside a browser we can't wait for the transition
6809
6828
  setChildrenVisible(false);
6810
6829
  }
@@ -6816,7 +6835,7 @@ const ExpansionPanel = forwardRef((props, ref) => {
6816
6835
  const {
6817
6836
  current: wrapper
6818
6837
  } = wrapperRef;
6819
- if (!IS_BROWSER || !wrapper) {
6838
+ if (!IS_BROWSER$1 || !wrapper) {
6820
6839
  return undefined;
6821
6840
  }
6822
6841
  const onTransitionEnd = () => {
@@ -14826,10 +14845,90 @@ Toolbar.displayName = TOOLBAR_NAME;
14826
14845
  Toolbar.className = CLASSNAME$3;
14827
14846
  Toolbar.defaultProps = DEFAULT_PROPS$3;
14828
14847
 
14848
+ /**
14849
+ * Arrow size (in pixel).
14850
+ */
14851
+ const ARROW_SIZE = 8;
14852
+
14829
14853
  /**
14830
14854
  * Make sure tooltip appear above popovers.
14855
+ * Hardcoded as POPOVER_ZINDEX (9999) + 1.
14856
+ */
14857
+ const TOOLTIP_ZINDEX = 10000;
14858
+
14859
+ /**
14860
+ * Component default props.
14861
+ */
14862
+ const DEFAULT_PROPS$2 = {
14863
+ placement: 'bottom',
14864
+ closeMode: 'unmount',
14865
+ ariaLinkMode: 'aria-describedby',
14866
+ zIndex: TOOLTIP_ZINDEX
14867
+ };
14868
+
14869
+ /**
14870
+ * Component display name.
14871
+ */
14872
+ const COMPONENT_NAME$2 = 'Tooltip';
14873
+
14874
+ /**
14875
+ * Component default class name and class prefix.
14876
+ */
14877
+ const CLASSNAME$2 = 'lumx-tooltip';
14878
+ const {
14879
+ block: block$2,
14880
+ element: element$2
14881
+ } = bem(CLASSNAME$2);
14882
+
14883
+ /**
14884
+ * Props for the TooltipPopup rendering component.
14831
14885
  */
14832
- const TOOLTIP_ZINDEX = POPOVER_ZINDEX + 1;
14886
+
14887
+ /**
14888
+ * Tooltip popup rendering component.
14889
+ * Pure JSX template for the tooltip popup element (arrow + inner text + BEM classes).
14890
+ *
14891
+ * @param props Component props.
14892
+ * @return JSX element.
14893
+ */
14894
+ const TooltipPopup = props => {
14895
+ const {
14896
+ id,
14897
+ label,
14898
+ position,
14899
+ isHidden,
14900
+ style,
14901
+ zIndex = TOOLTIP_ZINDEX,
14902
+ className,
14903
+ ref,
14904
+ ...forwardedProps
14905
+ } = props;
14906
+ const labelLines = label ? label.split('\n') : [];
14907
+ return /*#__PURE__*/jsxs("div", {
14908
+ ref: ref,
14909
+ ...forwardedProps,
14910
+ id: id,
14911
+ role: "tooltip",
14912
+ className: classnames(className, block$2({
14913
+ [`position-${position}`]: Boolean(position)
14914
+ }), isHidden && visuallyHidden()),
14915
+ style: {
14916
+ ...(isHidden ? undefined : style),
14917
+ zIndex
14918
+ },
14919
+ "data-popper-placement": position,
14920
+ children: [/*#__PURE__*/jsx("div", {
14921
+ className: element$2('arrow')
14922
+ }), /*#__PURE__*/jsx("div", {
14923
+ className: element$2('inner'),
14924
+ children: labelLines.map((line, index) => /*#__PURE__*/jsx("p", {
14925
+ children: line
14926
+ }, index))
14927
+ })]
14928
+ });
14929
+ };
14930
+ TooltipPopup.displayName = COMPONENT_NAME$2;
14931
+ TooltipPopup.className = CLASSNAME$2;
14833
14932
 
14834
14933
  /**
14835
14934
  * Add ref and ARIA attribute(s) in tooltip children or wrapped children.
@@ -14877,9 +14976,6 @@ const useInjectTooltipRef = options => {
14877
14976
  }, [label, children, setAnchorElement, linkId, ariaLinkMode]);
14878
14977
  };
14879
14978
 
14880
- /** Return true if the browser does not support pointer hover */
14881
- const isHoverNotSupported = () => !!window.matchMedia?.('(hover: none)').matches;
14882
-
14883
14979
  /** Check if the focus is visible on the given element */
14884
14980
  const isFocusVisible = element => {
14885
14981
  try {
@@ -14890,6 +14986,140 @@ const isFocusVisible = element => {
14890
14986
  }
14891
14987
  };
14892
14988
 
14989
+ /** Return true if the browser does not support pointer hover */
14990
+ const isHoverNotSupported = () => !!window.matchMedia?.('(hover: none)').matches;
14991
+
14992
+ /**
14993
+ * Framework-agnostic open/close state machine for tooltip.
14994
+ * Manages hover, touch, focus, timers, and escape key behavior.
14995
+ */
14996
+ function createTooltipOpenManager(options) {
14997
+ const {
14998
+ delay,
14999
+ onStateChange
15000
+ } = options;
15001
+ let timer;
15002
+ let openStartTime;
15003
+ let shouldOpen;
15004
+ let anchorController;
15005
+ let popperController;
15006
+
15007
+ // Run timer to defer updating the isOpen state.
15008
+ const deferUpdate = duration => {
15009
+ if (timer) clearTimeout(timer);
15010
+ const update = () => {
15011
+ onStateChange(!!shouldOpen);
15012
+ };
15013
+ // Skip timeout in fake browsers
15014
+ if (!IS_BROWSER) update();else timer = setTimeout(update, duration);
15015
+ };
15016
+ const hoverNotSupported = isHoverNotSupported();
15017
+ const hasTouch = typeof window !== 'undefined' && 'ontouchstart' in window;
15018
+
15019
+ // Adapt open/close delay
15020
+ const openDelay = delay || (hoverNotSupported ? TOOLTIP_LONG_PRESS_DELAY.open : TOOLTIP_HOVER_DELAY.open);
15021
+ const closeDelay = hoverNotSupported ? TOOLTIP_LONG_PRESS_DELAY.close : TOOLTIP_HOVER_DELAY.close;
15022
+
15023
+ // Open (or/and cancel closing) of tooltip.
15024
+ const open = () => {
15025
+ if (shouldOpen && !timer) return;
15026
+ shouldOpen = true;
15027
+ openStartTime = Date.now();
15028
+ deferUpdate(openDelay);
15029
+ };
15030
+
15031
+ // Close or cancel opening of tooltip
15032
+ const getClose = overrideDelay => {
15033
+ if (!shouldOpen && !timer) return;
15034
+ shouldOpen = false;
15035
+ deferUpdate(overrideDelay);
15036
+ };
15037
+ const close = () => getClose(closeDelay);
15038
+ const closeImmediately = () => getClose(0);
15039
+ return {
15040
+ attachAnchor(anchorElement) {
15041
+ anchorController = new AbortController();
15042
+ const {
15043
+ signal
15044
+ } = anchorController;
15045
+ if (hoverNotSupported) {
15046
+ /**
15047
+ * Handle touchend event.
15048
+ * If end comes before the open delay => cancel tooltip (close immediate).
15049
+ * Else if end comes after the open delay => tooltip takes priority, the anchor's default touch end event is prevented.
15050
+ */
15051
+ const longPressEnd = evt => {
15052
+ if (!openStartTime) return;
15053
+ if (Date.now() - openStartTime >= openDelay) {
15054
+ // Tooltip take priority, event prevented.
15055
+ evt.stopPropagation();
15056
+ evt.preventDefault();
15057
+ anchorElement.focus();
15058
+ // Close with delay.
15059
+ close();
15060
+ } else {
15061
+ // Close immediately.
15062
+ closeImmediately();
15063
+ }
15064
+ };
15065
+ anchorElement.addEventListener(hasTouch ? 'touchstart' : 'mousedown', open, {
15066
+ signal
15067
+ });
15068
+ anchorElement.addEventListener(hasTouch ? 'touchend' : 'mouseup', longPressEnd, {
15069
+ signal
15070
+ });
15071
+ } else {
15072
+ anchorElement.addEventListener('mouseenter', open, {
15073
+ signal
15074
+ });
15075
+ anchorElement.addEventListener('mouseleave', close, {
15076
+ signal
15077
+ });
15078
+ anchorElement.addEventListener('mouseup', closeImmediately, {
15079
+ signal
15080
+ });
15081
+ }
15082
+
15083
+ // Events always applied no matter the browser:
15084
+ // Open on focus (only if focus is visible).
15085
+ anchorElement.addEventListener('focusin', e => {
15086
+ // Skip if focus is not visible
15087
+ if (!isFocusVisible(e.target)) return;
15088
+ open();
15089
+ }, {
15090
+ signal
15091
+ });
15092
+ // Close on lost focus.
15093
+ anchorElement.addEventListener('focusout', closeImmediately, {
15094
+ signal
15095
+ });
15096
+ },
15097
+ attachPopper(popperElement) {
15098
+ popperController?.abort();
15099
+ if (!popperElement || hoverNotSupported) return;
15100
+ popperController = new AbortController();
15101
+ const {
15102
+ signal
15103
+ } = popperController;
15104
+ // Popper element hover
15105
+ popperElement.addEventListener('mouseenter', open, {
15106
+ signal
15107
+ });
15108
+ popperElement.addEventListener('mouseleave', close, {
15109
+ signal
15110
+ });
15111
+ },
15112
+ close() {
15113
+ closeImmediately();
15114
+ },
15115
+ destroy() {
15116
+ if (timer) clearTimeout(timer);
15117
+ anchorController?.abort();
15118
+ popperController?.abort();
15119
+ }
15120
+ };
15121
+ }
15122
+
14893
15123
  /**
14894
15124
  * Hook controlling tooltip visibility using mouse hover the anchor and delay.
14895
15125
  *
@@ -14900,110 +15130,23 @@ const isFocusVisible = element => {
14900
15130
  function useTooltipOpen(delay, anchorElement) {
14901
15131
  const [isOpen, setIsOpen] = useState(false);
14902
15132
  const onPopperMount = useRef(null);
15133
+ const managerRef = useRef(null);
14903
15134
 
14904
15135
  // Global close on escape
14905
- const [closeCallback, setCloseCallback] = useState(undefined);
14906
- useCallbackOnEscape(isOpen ? closeCallback : undefined);
15136
+ useCallbackOnEscape(isOpen ? () => managerRef.current?.close() : undefined);
14907
15137
  useEffect(() => {
14908
15138
  if (!anchorElement) {
14909
15139
  return undefined;
14910
15140
  }
14911
- let timer;
14912
- let openStartTime;
14913
- let shouldOpen;
14914
-
14915
- // Run timer to defer updating the isOpen state.
14916
- const deferUpdate = duration => {
14917
- if (timer) clearTimeout(timer);
14918
- const update = () => {
14919
- setIsOpen(!!shouldOpen);
14920
- };
14921
- // Skip timeout in fake browsers
14922
- if (!IS_BROWSER) update();else timer = setTimeout(update, duration);
14923
- };
14924
- const hoverNotSupported = isHoverNotSupported();
14925
- const hasTouch = 'ontouchstart' in window;
14926
-
14927
- // Adapt open/close delay
14928
- const openDelay = delay || (hoverNotSupported ? TOOLTIP_LONG_PRESS_DELAY.open : TOOLTIP_HOVER_DELAY.open);
14929
- const closeDelay = hoverNotSupported ? TOOLTIP_LONG_PRESS_DELAY.close : TOOLTIP_HOVER_DELAY.close;
14930
-
14931
- // Open (or/and cancel closing) of tooltip.
14932
- const open = () => {
14933
- if (shouldOpen && !timer) return;
14934
- shouldOpen = true;
14935
- openStartTime = Date.now();
14936
- deferUpdate(openDelay);
14937
- };
14938
-
14939
- // Close or cancel opening of tooltip
14940
- const getClose = (overrideDelay = closeDelay) => {
14941
- if (!shouldOpen && !timer) return;
14942
- shouldOpen = false;
14943
- deferUpdate(overrideDelay);
14944
- };
14945
- const close = () => getClose(closeDelay);
14946
- const closeImmediately = () => getClose(0);
14947
- setCloseCallback(() => closeImmediately);
14948
-
14949
- // Adapt event to browsers with or without `hover` support.
14950
- const events = [];
14951
- if (hoverNotSupported) {
14952
- /**
14953
- * Handle touchend event
14954
- * If end comes before the open delay => cancel tooltip (close immediate).
14955
- * Else if end comes after the open delay => tooltip takes priority, the anchor's default touch end event is prevented.
14956
- */
14957
- const longPressEnd = evt => {
14958
- if (!openStartTime) return;
14959
- if (Date.now() - openStartTime >= openDelay) {
14960
- // Tooltip take priority, event prevented.
14961
- evt.stopPropagation();
14962
- evt.preventDefault();
14963
- anchorElement.focus();
14964
- // Close with delay.
14965
- close();
14966
- } else {
14967
- // Close immediately.
14968
- closeImmediately();
14969
- }
14970
- };
14971
- events.push([anchorElement, hasTouch ? 'touchstart' : 'mousedown', open], [anchorElement, hasTouch ? 'touchend' : 'mouseup', longPressEnd]);
14972
- } else {
14973
- events.push([anchorElement, 'mouseenter', open], [anchorElement, 'mouseleave', close], [anchorElement, 'mouseup', closeImmediately]);
14974
- onPopperMount.current = popperElement => {
14975
- if (!popperElement) return;
14976
- // Popper element hover
14977
- popperElement.addEventListener('mouseenter', open);
14978
- popperElement.addEventListener('mouseleave', close);
14979
- // Add to event list to remove on unmount
14980
- events.push([popperElement, 'mouseenter', open], [popperElement, 'mouseleave', close]);
14981
- };
14982
- }
14983
-
14984
- // Events always applied no matter the browser:.
14985
- events.push(
14986
- // Open on focus (only if focus is visible).
14987
- [anchorElement, 'focusin', e => {
14988
- // Skip if focus is not visible
14989
- if (!isFocusVisible(e.target)) return;
14990
- open();
14991
- }],
14992
- // Close on lost focus.
14993
- [anchorElement, 'focusout', closeImmediately]);
14994
-
14995
- // Attach events
14996
- for (const [node, eventType, eventHandler] of events) {
14997
- node.addEventListener(eventType, eventHandler);
14998
- }
15141
+ const manager = createTooltipOpenManager({
15142
+ delay,
15143
+ onStateChange: setIsOpen
15144
+ });
15145
+ managerRef.current = manager;
15146
+ onPopperMount.current = el => manager.attachPopper(el);
15147
+ manager.attachAnchor(anchorElement);
14999
15148
  return () => {
15000
- // Clear pending timers.
15001
- if (timer) clearTimeout(timer);
15002
-
15003
- // Detach events.
15004
- for (const [node, eventType, eventHandler] of events) {
15005
- node.removeEventListener(eventType, eventHandler);
15006
- }
15149
+ manager.destroy();
15007
15150
  };
15008
15151
  }, [anchorElement, delay]);
15009
15152
  return {
@@ -15013,35 +15156,6 @@ function useTooltipOpen(delay, anchorElement) {
15013
15156
  }
15014
15157
 
15015
15158
  /* eslint-disable react-hooks/rules-of-hooks */
15016
- /**
15017
- * Component display name.
15018
- */
15019
- const COMPONENT_NAME$2 = 'Tooltip';
15020
-
15021
- /**
15022
- * Component default class name and class prefix.
15023
- */
15024
- const CLASSNAME$2 = 'lumx-tooltip';
15025
- const {
15026
- block: block$2,
15027
- element: element$2
15028
- } = classNames.bem(CLASSNAME$2);
15029
-
15030
- /**
15031
- * Component default props.
15032
- */
15033
- const DEFAULT_PROPS$2 = {
15034
- placement: Placement.BOTTOM,
15035
- closeMode: 'unmount',
15036
- ariaLinkMode: 'aria-describedby',
15037
- zIndex: TOOLTIP_ZINDEX
15038
- };
15039
-
15040
- /**
15041
- * Arrow size (in pixel).
15042
- */
15043
- const ARROW_SIZE = 8;
15044
-
15045
15159
  /**
15046
15160
  * Tooltip component.
15047
15161
  *
@@ -15099,33 +15213,21 @@ const Tooltip = forwardRef((props, ref) => {
15099
15213
  label,
15100
15214
  ariaLinkMode: ariaLinkMode
15101
15215
  });
15102
- const labelLines = label ? label.split('\n') : [];
15103
15216
  const tooltipRef = useMergeRefs(ref, setPopperElement, onPopperMount);
15104
15217
  return /*#__PURE__*/jsxs(Fragment, {
15105
15218
  children: [/*#__PURE__*/jsx(TooltipContextProvider, {
15106
15219
  children: wrappedChildren
15107
15220
  }), isMounted && /*#__PURE__*/jsx(Portal, {
15108
- children: /*#__PURE__*/jsxs("div", {
15221
+ children: TooltipPopup({
15109
15222
  ref: tooltipRef,
15110
15223
  ...forwardedProps,
15111
- id: id,
15112
- role: "tooltip",
15113
- className: classNames.join(className, block$2({
15114
- [`position-${position}`]: Boolean(position)
15115
- }), isHidden && classNames.visuallyHidden()),
15116
- style: {
15117
- ...(isHidden ? undefined : floatingStyles),
15118
- zIndex
15119
- },
15120
- "data-popper-placement": position,
15121
- children: [/*#__PURE__*/jsx("div", {
15122
- className: element$2('arrow')
15123
- }), /*#__PURE__*/jsx("div", {
15124
- className: element$2('inner'),
15125
- children: labelLines.map(line => /*#__PURE__*/jsx("p", {
15126
- children: line
15127
- }, line))
15128
- })]
15224
+ id,
15225
+ label: label,
15226
+ position: position,
15227
+ isHidden,
15228
+ style: isHidden ? undefined : floatingStyles,
15229
+ zIndex,
15230
+ className
15129
15231
  })
15130
15232
  })]
15131
15233
  });