@lumx/react 3.6.0 → 3.6.1-alpha-chore-dependency-updates.0
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 +91 -66
- package/index.js.map +1 -1
- package/package.json +10 -12
- package/src/components/tooltip/Tooltip.stories.tsx +3 -2
- package/src/components/tooltip/Tooltip.test.tsx +32 -0
- package/src/components/tooltip/Tooltip.tsx +4 -3
- package/src/components/tooltip/useInjectTooltipRef.tsx +1 -1
- package/src/components/tooltip/useTooltipOpen.tsx +56 -38
package/package.json
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
},
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@juggle/resize-observer": "^3.2.0",
|
|
10
|
-
"@lumx/core": "^3.6.0",
|
|
11
|
-
"@lumx/icons": "^3.6.0",
|
|
10
|
+
"@lumx/core": "^3.6.1-alpha-chore-dependency-updates.0",
|
|
11
|
+
"@lumx/icons": "^3.6.1-alpha-chore-dependency-updates.0",
|
|
12
12
|
"@popperjs/core": "^2.5.4",
|
|
13
13
|
"body-scroll-lock": "^3.1.5",
|
|
14
14
|
"classnames": "^2.3.2",
|
|
@@ -30,13 +30,12 @@
|
|
|
30
30
|
"@babel/preset-typescript": "^7.18.6",
|
|
31
31
|
"@rollup/plugin-commonjs": "^15.0.0",
|
|
32
32
|
"@rollup/plugin-node-resolve": "9.0.0",
|
|
33
|
-
"@storybook/addon-a11y": "^7.
|
|
34
|
-
"@storybook/addon-essentials": "^7.
|
|
35
|
-
"@storybook/addon-interactions": "^7.
|
|
36
|
-
"@storybook/blocks": "^7.
|
|
37
|
-
"@storybook/react": "^7.
|
|
38
|
-
"@storybook/react-vite": "^7.
|
|
39
|
-
"@storybook/testing-library": "^0.0.14-next.2",
|
|
33
|
+
"@storybook/addon-a11y": "^7.6.3",
|
|
34
|
+
"@storybook/addon-essentials": "^7.6.3",
|
|
35
|
+
"@storybook/addon-interactions": "^7.6.3",
|
|
36
|
+
"@storybook/blocks": "^7.6.3",
|
|
37
|
+
"@storybook/react": "^7.6.3",
|
|
38
|
+
"@storybook/react-vite": "^7.6.3",
|
|
40
39
|
"@testing-library/jest-dom": "^5.16.4",
|
|
41
40
|
"@testing-library/react": "^12.1.2",
|
|
42
41
|
"@testing-library/user-event": "^14.4.3",
|
|
@@ -69,8 +68,7 @@
|
|
|
69
68
|
"rollup-plugin-peer-deps-external": "^2.2.4",
|
|
70
69
|
"rollup-plugin-ts-paths-resolve": "^1.3.0",
|
|
71
70
|
"rollup-plugin-typescript-paths": "^1.2.2",
|
|
72
|
-
"storybook": "^7.
|
|
73
|
-
"tsconfig-paths-webpack-plugin": "^3.3.0",
|
|
71
|
+
"storybook": "^7.6.3",
|
|
74
72
|
"typescript": "^4.1.2",
|
|
75
73
|
"vite": "^4.2.1",
|
|
76
74
|
"vite-tsconfig-paths": "^4.0.7",
|
|
@@ -115,5 +113,5 @@
|
|
|
115
113
|
"build:storybook": "storybook build"
|
|
116
114
|
},
|
|
117
115
|
"sideEffects": false,
|
|
118
|
-
"version": "3.6.0"
|
|
116
|
+
"version": "3.6.1-alpha-chore-dependency-updates.0"
|
|
119
117
|
}
|
|
@@ -56,12 +56,13 @@ export const TooltipWithDropdown = (props: any) => {
|
|
|
56
56
|
const [isOpen, setOpen] = useState(false);
|
|
57
57
|
return (
|
|
58
58
|
<>
|
|
59
|
-
<
|
|
59
|
+
<br />
|
|
60
|
+
<Tooltip label={!isOpen && 'Tooltip'} {...props} placement="top">
|
|
60
61
|
<Button ref={setButton} onClick={() => setOpen((o) => !o)}>
|
|
61
62
|
Anchor
|
|
62
63
|
</Button>
|
|
63
64
|
</Tooltip>
|
|
64
|
-
<Dropdown anchorRef={{ current: button }} isOpen={isOpen}>
|
|
65
|
+
<Dropdown anchorRef={{ current: button }} isOpen={isOpen} onClose={() => setOpen(false)}>
|
|
65
66
|
Dropdown
|
|
66
67
|
</Dropdown>
|
|
67
68
|
</>
|
|
@@ -125,6 +125,38 @@ describe(`<${Tooltip.displayName}>`, () => {
|
|
|
125
125
|
});
|
|
126
126
|
});
|
|
127
127
|
|
|
128
|
+
it('should activate on hover anchor and then tooltip', async () => {
|
|
129
|
+
let { tooltip } = await setup({
|
|
130
|
+
label: 'Tooltip label',
|
|
131
|
+
children: <Button>Anchor</Button>,
|
|
132
|
+
forceOpen: false,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
expect(tooltip).not.toBeInTheDocument();
|
|
136
|
+
|
|
137
|
+
// Hover anchor button
|
|
138
|
+
const button = getByClassName(document.body, Button.className as string);
|
|
139
|
+
await userEvent.hover(button);
|
|
140
|
+
|
|
141
|
+
// Tooltip opened
|
|
142
|
+
tooltip = await findByClassName(document.body, CLASSNAME);
|
|
143
|
+
expect(tooltip).toBeInTheDocument();
|
|
144
|
+
expect(button).toHaveAttribute('aria-describedby', tooltip?.id);
|
|
145
|
+
|
|
146
|
+
// Hover tooltip
|
|
147
|
+
await userEvent.hover(tooltip);
|
|
148
|
+
expect(tooltip).toBeInTheDocument();
|
|
149
|
+
expect(button).toHaveAttribute('aria-describedby', tooltip?.id);
|
|
150
|
+
|
|
151
|
+
// Un-hover tooltip
|
|
152
|
+
userEvent.unhover(tooltip);
|
|
153
|
+
await waitFor(() => {
|
|
154
|
+
expect(button).not.toHaveFocus();
|
|
155
|
+
// Tooltip closed
|
|
156
|
+
expect(tooltip).not.toBeInTheDocument();
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
128
160
|
it('should activate on anchor focus', async () => {
|
|
129
161
|
let { tooltip } = await setup({
|
|
130
162
|
label: 'Tooltip label',
|
|
@@ -85,8 +85,9 @@ export const Tooltip: Comp<TooltipProps, HTMLDivElement> = forwardRef((props, re
|
|
|
85
85
|
});
|
|
86
86
|
|
|
87
87
|
const position = attributes?.popper?.['data-popper-placement'] ?? placement;
|
|
88
|
-
const isOpen = useTooltipOpen(delay, anchorElement)
|
|
89
|
-
const
|
|
88
|
+
const { isOpen: isActivated, onPopperMount } = useTooltipOpen(delay, anchorElement);
|
|
89
|
+
const isOpen = isActivated || forceOpen;
|
|
90
|
+
const wrappedChildren = useInjectTooltipRef(children, setAnchorElement, isOpen, id);
|
|
90
91
|
|
|
91
92
|
return (
|
|
92
93
|
<>
|
|
@@ -94,7 +95,7 @@ export const Tooltip: Comp<TooltipProps, HTMLDivElement> = forwardRef((props, re
|
|
|
94
95
|
{isOpen &&
|
|
95
96
|
createPortal(
|
|
96
97
|
<div
|
|
97
|
-
ref={mergeRefs(ref, setPopperElement)}
|
|
98
|
+
ref={mergeRefs(ref, setPopperElement, onPopperMount)}
|
|
98
99
|
{...forwardedProps}
|
|
99
100
|
id={id}
|
|
100
101
|
role="tooltip"
|
|
@@ -18,7 +18,7 @@ import { mergeRefs } from '@lumx/react/utils/mergeRefs';
|
|
|
18
18
|
export const useInjectTooltipRef = (
|
|
19
19
|
children: ReactNode,
|
|
20
20
|
setAnchorElement: (e: HTMLDivElement) => void,
|
|
21
|
-
isOpen: boolean,
|
|
21
|
+
isOpen: boolean | undefined,
|
|
22
22
|
id: string,
|
|
23
23
|
): ReactNode => {
|
|
24
24
|
return useMemo(() => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useEffect, useState } from 'react';
|
|
1
|
+
import { MutableRefObject, useEffect, useRef, useState } from 'react';
|
|
3
2
|
import { browserDoesNotSupportHover } from '@lumx/react/utils/browserDoesNotSupportHover';
|
|
4
3
|
import { TOOLTIP_HOVER_DELAY, TOOLTIP_LONG_PRESS_DELAY } from '@lumx/react/constants';
|
|
4
|
+
import { useCallbackOnEscape } from '@lumx/react/hooks/useCallbackOnEscape';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Hook controlling tooltip visibility using mouse hover the anchor and delay.
|
|
@@ -10,9 +10,15 @@ import { TOOLTIP_HOVER_DELAY, TOOLTIP_LONG_PRESS_DELAY } from '@lumx/react/const
|
|
|
10
10
|
* @param anchorElement Tooltip anchor element.
|
|
11
11
|
* @return whether or not to show the tooltip.
|
|
12
12
|
*/
|
|
13
|
-
export function useTooltipOpen(delay: number | undefined, anchorElement: HTMLElement | null)
|
|
13
|
+
export function useTooltipOpen(delay: number | undefined, anchorElement: HTMLElement | null) {
|
|
14
14
|
const [isOpen, setIsOpen] = useState(false);
|
|
15
15
|
|
|
16
|
+
const onPopperMount = useRef<any>(null) as MutableRefObject<(elem: HTMLElement | null) => void>;
|
|
17
|
+
|
|
18
|
+
// Global close on escape
|
|
19
|
+
const [closeCallback, setCloseCallback] = useState<undefined | (() => void)>(undefined);
|
|
20
|
+
useCallbackOnEscape(closeCallback);
|
|
21
|
+
|
|
16
22
|
useEffect(() => {
|
|
17
23
|
if (!anchorElement) {
|
|
18
24
|
return undefined;
|
|
@@ -45,44 +51,58 @@ export function useTooltipOpen(delay: number | undefined, anchorElement: HTMLEle
|
|
|
45
51
|
};
|
|
46
52
|
|
|
47
53
|
// Close or cancel opening of tooltip
|
|
48
|
-
const
|
|
54
|
+
const getClose = (overrideDelay = closeDelay) => {
|
|
49
55
|
if (!shouldOpen && !timer) return;
|
|
50
56
|
shouldOpen = false;
|
|
51
57
|
deferUpdate(overrideDelay);
|
|
52
58
|
};
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
* Handle touchend event
|
|
57
|
-
* If `touchend` comes before the open delay => cancel tooltip (close immediate).
|
|
58
|
-
* Else if `touchend` comes after the open delay => tooltip takes priority, the anchor's default touch end event is prevented.
|
|
59
|
-
*/
|
|
60
|
-
const touchEnd = (evt: Event) => {
|
|
61
|
-
if (!openStartTime) return;
|
|
62
|
-
if (Date.now() - openStartTime >= openDelay) {
|
|
63
|
-
// Tooltip take priority, event prevented.
|
|
64
|
-
evt.stopPropagation();
|
|
65
|
-
evt.preventDefault();
|
|
66
|
-
anchorElement.focus();
|
|
67
|
-
// Close with delay.
|
|
68
|
-
close();
|
|
69
|
-
} else {
|
|
70
|
-
// Close immediately.
|
|
71
|
-
closeImmediately();
|
|
72
|
-
}
|
|
73
|
-
};
|
|
59
|
+
const close = () => getClose(closeDelay);
|
|
60
|
+
const closeImmediately = () => getClose(0);
|
|
61
|
+
setCloseCallback(() => closeImmediately);
|
|
74
62
|
|
|
75
63
|
// Adapt event to browsers with or without `hover` support.
|
|
76
|
-
const events: Array<[Node, Event['type'], any]> =
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
64
|
+
const events: Array<[Node, Event['type'], any]> = [];
|
|
65
|
+
if (hoverNotSupported) {
|
|
66
|
+
/**
|
|
67
|
+
* Handle touchend event
|
|
68
|
+
* If end comes before the open delay => cancel tooltip (close immediate).
|
|
69
|
+
* Else if end comes after the open delay => tooltip takes priority, the anchor's default touch end event is prevented.
|
|
70
|
+
*/
|
|
71
|
+
const longPressEnd = (evt: Event) => {
|
|
72
|
+
if (!openStartTime) return;
|
|
73
|
+
if (Date.now() - openStartTime >= openDelay) {
|
|
74
|
+
// Tooltip take priority, event prevented.
|
|
75
|
+
evt.stopPropagation();
|
|
76
|
+
evt.preventDefault();
|
|
77
|
+
anchorElement.focus();
|
|
78
|
+
// Close with delay.
|
|
79
|
+
close();
|
|
80
|
+
} else {
|
|
81
|
+
// Close immediately.
|
|
82
|
+
closeImmediately();
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
events.push(
|
|
87
|
+
[anchorElement, hasTouch ? 'touchstart' : 'mousedown', open],
|
|
88
|
+
[anchorElement, hasTouch ? 'touchend' : 'mouseup', longPressEnd],
|
|
89
|
+
);
|
|
90
|
+
} else {
|
|
91
|
+
events.push(
|
|
92
|
+
[anchorElement, 'mouseenter', open],
|
|
93
|
+
[anchorElement, 'mouseleave', close],
|
|
94
|
+
[anchorElement, 'mouseup', closeImmediately],
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
onPopperMount.current = (popperElement: HTMLElement | null) => {
|
|
98
|
+
if (!popperElement) return;
|
|
99
|
+
// Popper element hover
|
|
100
|
+
popperElement.addEventListener('mouseenter', open);
|
|
101
|
+
popperElement.addEventListener('mouseleave', close);
|
|
102
|
+
// Add to event list to remove on unmount
|
|
103
|
+
events.push([popperElement, 'mouseenter', open], [popperElement, 'mouseleave', close]);
|
|
104
|
+
};
|
|
105
|
+
}
|
|
86
106
|
|
|
87
107
|
// Events always applied no matter the browser:.
|
|
88
108
|
events.push(
|
|
@@ -90,8 +110,6 @@ export function useTooltipOpen(delay: number | undefined, anchorElement: HTMLEle
|
|
|
90
110
|
[anchorElement, 'focusin', open],
|
|
91
111
|
// Close on lost focus.
|
|
92
112
|
[anchorElement, 'focusout', closeImmediately],
|
|
93
|
-
// Close on ESC keydown
|
|
94
|
-
[anchorElement, 'keydown', onEscapePressed(closeImmediately)],
|
|
95
113
|
);
|
|
96
114
|
|
|
97
115
|
// Attach events
|
|
@@ -109,5 +127,5 @@ export function useTooltipOpen(delay: number | undefined, anchorElement: HTMLEle
|
|
|
109
127
|
};
|
|
110
128
|
}, [anchorElement, delay]);
|
|
111
129
|
|
|
112
|
-
return isOpen;
|
|
130
|
+
return { isOpen, onPopperMount: onPopperMount.current };
|
|
113
131
|
}
|