@lumx/react 3.6.5-alpha.2 → 3.6.5-alpha.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 +5 -0
- package/index.js +69 -51
- package/index.js.map +1 -1
- package/package.json +3 -3
- package/src/components/autocomplete/Autocomplete.tsx +7 -0
- package/src/components/popover/useRestoreFocusOnClose.tsx +29 -17
- package/src/components/popover-dialog/PopoverDialog.stories.tsx +18 -18
- package/src/components/popover-dialog/PopoverDialog.test.tsx +58 -45
- package/src/components/tooltip/Tooltip.test.tsx +19 -0
- package/src/components/tooltip/useInjectTooltipRef.tsx +11 -11
- package/src/utils/OnBeforeUnmount.tsx +20 -0
- package/src/utils/isFocusVisible.ts +8 -2
- package/src/utils/useBeforeUnmountSentinel.tsx +0 -17
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.5-alpha.
|
|
11
|
-
"@lumx/icons": "^3.6.5-alpha.
|
|
10
|
+
"@lumx/core": "^3.6.5-alpha.4",
|
|
11
|
+
"@lumx/icons": "^3.6.5-alpha.4",
|
|
12
12
|
"@popperjs/core": "^2.5.4",
|
|
13
13
|
"body-scroll-lock": "^3.1.5",
|
|
14
14
|
"classnames": "^2.3.2",
|
|
@@ -112,5 +112,5 @@
|
|
|
112
112
|
"build:storybook": "storybook build"
|
|
113
113
|
},
|
|
114
114
|
"sideEffects": false,
|
|
115
|
-
"version": "3.6.5-alpha.
|
|
115
|
+
"version": "3.6.5-alpha.4"
|
|
116
116
|
}
|
|
@@ -133,6 +133,11 @@ export interface AutocompleteProps extends GenericProps, HasTheme {
|
|
|
133
133
|
* @see {@link DropdownProps#closeOnEscape}
|
|
134
134
|
*/
|
|
135
135
|
closeOnEscape?: boolean;
|
|
136
|
+
/**
|
|
137
|
+
* Whether the focus should go back on the anchor when dropdown closes and focus is within.
|
|
138
|
+
* @see {@link DropdownProps#focusAnchorOnClose}
|
|
139
|
+
*/
|
|
140
|
+
focusAnchorOnClose?: DropdownProps['focusAnchorOnClose'];
|
|
136
141
|
/**
|
|
137
142
|
* The function called on blur.
|
|
138
143
|
* @see {@link TextFieldProps#onBlur}
|
|
@@ -229,6 +234,7 @@ export const Autocomplete: Comp<AutocompleteProps, HTMLDivElement> = forwardRef(
|
|
|
229
234
|
theme,
|
|
230
235
|
value,
|
|
231
236
|
textFieldProps = {},
|
|
237
|
+
focusAnchorOnClose,
|
|
232
238
|
...forwardedProps
|
|
233
239
|
} = props;
|
|
234
240
|
const inputAnchorRef = useRef<HTMLElement>(null);
|
|
@@ -273,6 +279,7 @@ export const Autocomplete: Comp<AutocompleteProps, HTMLDivElement> = forwardRef(
|
|
|
273
279
|
closeOnClick={closeOnClick}
|
|
274
280
|
closeOnClickAway={closeOnClickAway}
|
|
275
281
|
closeOnEscape={closeOnEscape}
|
|
282
|
+
focusAnchorOnClose={focusAnchorOnClose}
|
|
276
283
|
fitToAnchorWidth={fitToAnchorWidth}
|
|
277
284
|
isOpen={isOpen}
|
|
278
285
|
offset={offset}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { getFirstAndLastFocusable } from '@lumx/react/utils/focus/getFirstAndLastFocusable';
|
|
3
|
-
import {
|
|
3
|
+
import { OnBeforeUnmount } from '@lumx/react/utils/OnBeforeUnmount';
|
|
4
4
|
import type { PopoverProps } from './Popover';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -8,26 +8,38 @@ import type { PopoverProps } from './Popover';
|
|
|
8
8
|
* anchor if needed.
|
|
9
9
|
*/
|
|
10
10
|
export function useRestoreFocusOnClose(
|
|
11
|
-
|
|
11
|
+
{
|
|
12
|
+
focusAnchorOnClose,
|
|
13
|
+
anchorRef,
|
|
14
|
+
parentElement,
|
|
15
|
+
}: Pick<PopoverProps, 'focusAnchorOnClose' | 'anchorRef' | 'parentElement'>,
|
|
12
16
|
popoverElement?: HTMLElement | null,
|
|
13
17
|
) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
19
|
+
const onBeforeUnmountRef = React.useRef<() => void>(() => {});
|
|
20
|
+
|
|
21
|
+
const anchor = anchorRef.current;
|
|
22
|
+
const elementToFocus =
|
|
23
|
+
// Provided parent element
|
|
24
|
+
parentElement?.current ||
|
|
25
|
+
// Or first focusable element in anchor
|
|
26
|
+
(anchor ? getFirstAndLastFocusable(anchor).first : undefined) ||
|
|
27
|
+
// Fallback to anchor
|
|
28
|
+
anchor;
|
|
29
|
+
|
|
30
|
+
React.useEffect(() => {
|
|
31
|
+
if (!popoverElement || !focusAnchorOnClose || !elementToFocus) return;
|
|
32
|
+
|
|
33
|
+
onBeforeUnmountRef.current = () => {
|
|
17
34
|
const isFocusWithin = popoverElement?.contains(document.activeElement);
|
|
18
35
|
if (!isFocusWithin) return;
|
|
19
36
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
// Or first focusable element in anchor
|
|
25
|
-
(anchor ? getFirstAndLastFocusable(anchor).first : undefined) ||
|
|
26
|
-
// Fallback to anchor
|
|
27
|
-
anchor;
|
|
28
|
-
|
|
29
|
-
elementToFocus?.focus({ preventScroll: true });
|
|
37
|
+
// Focus on next render
|
|
38
|
+
setTimeout(() => {
|
|
39
|
+
elementToFocus.focus({ preventScroll: true });
|
|
40
|
+
}, 0);
|
|
30
41
|
};
|
|
31
|
-
}, [
|
|
32
|
-
|
|
42
|
+
}, [anchor, elementToFocus, focusAnchorOnClose, popoverElement]);
|
|
43
|
+
|
|
44
|
+
return <OnBeforeUnmount callback={onBeforeUnmountRef} />;
|
|
33
45
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { useBooleanState } from '@lumx/react/hooks/useBooleanState';
|
|
3
|
-
import { mdiMenuDown } from '@lumx/icons';
|
|
3
|
+
import { mdiMenuDown } from '@lumx/icons/';
|
|
4
4
|
import { PopoverDialog } from '.';
|
|
5
5
|
import { Button, IconButton } from '../button';
|
|
6
6
|
|
|
@@ -11,52 +11,52 @@ export default {
|
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* Example PopoverDialog using
|
|
14
|
+
* Example PopoverDialog using a button as a trigger
|
|
15
15
|
*/
|
|
16
|
-
export const
|
|
16
|
+
export const WithButtonTrigger = (props: any) => {
|
|
17
17
|
const anchorRef = React.useRef(null);
|
|
18
18
|
const [isOpen, close, open] = useBooleanState(false);
|
|
19
19
|
|
|
20
20
|
return (
|
|
21
21
|
<>
|
|
22
|
-
<
|
|
22
|
+
<Button ref={anchorRef} onClick={open}>
|
|
23
|
+
Open popover
|
|
24
|
+
</Button>
|
|
23
25
|
<PopoverDialog
|
|
24
|
-
aria-labelledby="trigger-button-1"
|
|
25
26
|
anchorRef={anchorRef}
|
|
26
27
|
isOpen={isOpen}
|
|
27
28
|
onClose={close}
|
|
28
29
|
placement="bottom"
|
|
30
|
+
className="lumx-spacing-padding-huge"
|
|
31
|
+
{...props}
|
|
29
32
|
>
|
|
30
|
-
<Button
|
|
31
|
-
|
|
32
|
-
</Button>
|
|
33
|
+
<Button onClick={close}>Close</Button>
|
|
34
|
+
<Button emphasis="medium">Other button</Button>
|
|
33
35
|
</PopoverDialog>
|
|
34
36
|
</>
|
|
35
37
|
);
|
|
36
38
|
};
|
|
37
39
|
|
|
38
40
|
/**
|
|
39
|
-
* Example PopoverDialog using
|
|
41
|
+
* Example PopoverDialog using an icon button as a trigger
|
|
40
42
|
*/
|
|
41
|
-
export const
|
|
43
|
+
export const WithIconButtonTrigger = (props: any) => {
|
|
42
44
|
const anchorRef = React.useRef(null);
|
|
43
|
-
const [isOpen, close, open] = useBooleanState(
|
|
45
|
+
const [isOpen, close, open] = useBooleanState(false);
|
|
44
46
|
|
|
45
47
|
return (
|
|
46
48
|
<>
|
|
47
|
-
<
|
|
48
|
-
Open popover
|
|
49
|
-
</Button>
|
|
49
|
+
<IconButton label="Open popover" ref={anchorRef} onClick={open} icon={mdiMenuDown} />
|
|
50
50
|
<PopoverDialog
|
|
51
|
-
aria-labelledby="trigger-button-1"
|
|
52
51
|
anchorRef={anchorRef}
|
|
53
52
|
isOpen={isOpen}
|
|
54
53
|
onClose={close}
|
|
55
54
|
placement="bottom"
|
|
55
|
+
className="lumx-spacing-padding-huge"
|
|
56
|
+
label="Example popover"
|
|
57
|
+
{...props}
|
|
56
58
|
>
|
|
57
|
-
<Button
|
|
58
|
-
Close
|
|
59
|
-
</Button>
|
|
59
|
+
<Button onClick={close}>Close</Button>
|
|
60
60
|
</PopoverDialog>
|
|
61
61
|
</>
|
|
62
62
|
);
|
|
@@ -1,50 +1,38 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from 'react';
|
|
2
2
|
import { render, screen, within } from '@testing-library/react';
|
|
3
3
|
import userEvent from '@testing-library/user-event';
|
|
4
|
-
import { isFocusVisible } from '@lumx/react/utils/isFocusVisible';
|
|
5
4
|
|
|
6
|
-
import { WithIconButtonTrigger } from './PopoverDialog.stories';
|
|
7
5
|
import { PopoverDialog } from './PopoverDialog';
|
|
6
|
+
import { WithButtonTrigger, WithIconButtonTrigger } from './PopoverDialog.stories';
|
|
8
7
|
|
|
9
8
|
jest.mock('@lumx/react/utils/isFocusVisible');
|
|
10
9
|
|
|
11
|
-
const DialogWithButton = (forwardedProps: any) => {
|
|
12
|
-
const anchorRef = useRef(null);
|
|
13
|
-
const [isOpen, setIsOpen] = useState<boolean>(false);
|
|
14
|
-
|
|
15
|
-
return (
|
|
16
|
-
<>
|
|
17
|
-
<button type="button" ref={anchorRef} onClick={() => setIsOpen((current) => !current)}>
|
|
18
|
-
Open popover
|
|
19
|
-
</button>
|
|
20
|
-
|
|
21
|
-
<PopoverDialog {...forwardedProps} anchorRef={anchorRef} isOpen={isOpen} onClose={() => setIsOpen(false)}>
|
|
22
|
-
<button type="button">Button 1</button>
|
|
23
|
-
<button type="button">Button 2</button>
|
|
24
|
-
</PopoverDialog>
|
|
25
|
-
{/* This should never have focus while popover is opened */}
|
|
26
|
-
<button type="button">External button</button>
|
|
27
|
-
</>
|
|
28
|
-
);
|
|
29
|
-
};
|
|
30
|
-
|
|
31
10
|
describe(`<${PopoverDialog.displayName}>`, () => {
|
|
32
|
-
(
|
|
33
|
-
|
|
34
|
-
it('should behave like a dialog', async () => {
|
|
11
|
+
it('should open and init focus', async () => {
|
|
35
12
|
const label = 'Test Label';
|
|
13
|
+
render(<WithButtonTrigger label={label} />);
|
|
14
|
+
|
|
15
|
+
// Open popover
|
|
16
|
+
const triggerElement = screen.getByRole('button', { name: 'Open popover' });
|
|
17
|
+
await userEvent.click(triggerElement);
|
|
18
|
+
|
|
19
|
+
const dialog = await screen.findByRole('dialog', { name: label });
|
|
20
|
+
|
|
21
|
+
// Focused the first button
|
|
22
|
+
expect(within(dialog).getAllByRole('button')[0]).toHaveFocus();
|
|
23
|
+
});
|
|
36
24
|
|
|
37
|
-
|
|
25
|
+
it('should trap focus', async () => {
|
|
26
|
+
const label = 'Test Label';
|
|
27
|
+
render(<WithButtonTrigger label={label} />);
|
|
38
28
|
|
|
39
|
-
|
|
29
|
+
// Open popover
|
|
40
30
|
const triggerElement = screen.getByRole('button', { name: 'Open popover' });
|
|
41
31
|
await userEvent.click(triggerElement);
|
|
42
32
|
|
|
43
33
|
const dialog = await screen.findByRole('dialog', { name: label });
|
|
44
|
-
const withinDialog = within(dialog);
|
|
45
34
|
|
|
46
|
-
|
|
47
|
-
const dialogButtons = withinDialog.getAllByRole('button');
|
|
35
|
+
const dialogButtons = within(dialog).getAllByRole('button');
|
|
48
36
|
|
|
49
37
|
// First button should have focus by default on opening
|
|
50
38
|
expect(dialogButtons[0]).toHaveFocus();
|
|
@@ -60,37 +48,62 @@ describe(`<${PopoverDialog.displayName}>`, () => {
|
|
|
60
48
|
|
|
61
49
|
// As there is no more button, focus should loop back to first button.
|
|
62
50
|
expect(dialogButtons[0]).toHaveFocus();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should close on escape and restore focus to trigger', async () => {
|
|
54
|
+
const label = 'Test Label';
|
|
55
|
+
render(<WithButtonTrigger label={label} />);
|
|
56
|
+
|
|
57
|
+
// Open popover
|
|
58
|
+
const triggerElement = screen.getByRole('button', { name: 'Open popover' });
|
|
59
|
+
await userEvent.click(triggerElement);
|
|
60
|
+
|
|
61
|
+
const dialog = await screen.findByRole('dialog', { name: label });
|
|
63
62
|
|
|
64
63
|
// Close the popover
|
|
65
64
|
await userEvent.keyboard('{escape}');
|
|
66
65
|
|
|
67
|
-
expect(
|
|
68
|
-
|
|
66
|
+
expect(dialog).not.toBeInTheDocument();
|
|
67
|
+
|
|
68
|
+
// Focus restored to the trigger element
|
|
69
69
|
expect(triggerElement).toHaveFocus();
|
|
70
70
|
});
|
|
71
71
|
|
|
72
|
-
it('should
|
|
73
|
-
const label = '
|
|
74
|
-
render(<
|
|
72
|
+
it('should close externally and restore focus to trigger', async () => {
|
|
73
|
+
const label = 'Test Label';
|
|
74
|
+
render(<WithButtonTrigger label={label} />);
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
const triggerElement = screen.getByRole('button', { name:
|
|
76
|
+
// Open popover
|
|
77
|
+
const triggerElement = screen.getByRole('button', { name: 'Open popover' });
|
|
78
78
|
await userEvent.click(triggerElement);
|
|
79
79
|
|
|
80
80
|
const dialog = await screen.findByRole('dialog', { name: label });
|
|
81
|
-
const withinDialog = within(dialog);
|
|
82
81
|
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
// Close the popover
|
|
83
|
+
await userEvent.click(screen.getByRole('button', { name: 'Close' }));
|
|
85
84
|
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
expect(dialog).not.toBeInTheDocument();
|
|
86
|
+
|
|
87
|
+
// Focus restored to the trigger element
|
|
88
|
+
expect(triggerElement).toHaveFocus();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should close on escape and restore focus to trigger having a tooltip', async () => {
|
|
92
|
+
const label = 'Test Label';
|
|
93
|
+
render(<WithIconButtonTrigger label={label} />);
|
|
94
|
+
|
|
95
|
+
// Open popover
|
|
96
|
+
const triggerElement = screen.getByRole('button', { name: 'Open popover' });
|
|
97
|
+
await userEvent.click(triggerElement);
|
|
98
|
+
|
|
99
|
+
const dialog = await screen.findByRole('dialog', { name: label });
|
|
88
100
|
|
|
89
101
|
// Close the popover
|
|
90
102
|
await userEvent.keyboard('{escape}');
|
|
91
103
|
|
|
92
|
-
expect(
|
|
93
|
-
|
|
104
|
+
expect(dialog).not.toBeInTheDocument();
|
|
105
|
+
|
|
106
|
+
// Focus restored to the trigger element
|
|
94
107
|
expect(triggerElement).toHaveFocus();
|
|
95
108
|
});
|
|
96
109
|
});
|
|
@@ -52,6 +52,25 @@ describe(`<${Tooltip.displayName}>`, () => {
|
|
|
52
52
|
expect(anchorWrapper).toHaveAttribute('aria-describedby', tooltip?.id);
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
+
it('should wrap unknown children and not add aria-describedby when closed', async () => {
|
|
56
|
+
const { anchorWrapper } = await setup({
|
|
57
|
+
label: 'Tooltip label',
|
|
58
|
+
children: 'Anchor',
|
|
59
|
+
forceOpen: false,
|
|
60
|
+
});
|
|
61
|
+
expect(anchorWrapper).not.toHaveAttribute('aria-describedby');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should not wrap Button and not add aria-describedby when closed', async () => {
|
|
65
|
+
await setup({
|
|
66
|
+
label: 'Tooltip label',
|
|
67
|
+
children: <Button>Anchor</Button>,
|
|
68
|
+
forceOpen: false,
|
|
69
|
+
});
|
|
70
|
+
const button = screen.queryByRole('button', { name: 'Anchor' });
|
|
71
|
+
expect(button).not.toHaveAttribute('aria-describedby');
|
|
72
|
+
});
|
|
73
|
+
|
|
55
74
|
it('should not wrap Button', async () => {
|
|
56
75
|
const { tooltip, anchorWrapper } = await setup({
|
|
57
76
|
label: 'Tooltip label',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { cloneElement, ReactNode, useMemo } from 'react';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { mergeRefs } from '@lumx/react/utils/mergeRefs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Add ref and ARIA attribute(s) in tooltip children or wrapped children.
|
|
@@ -21,27 +21,27 @@ export const useInjectTooltipRef = (
|
|
|
21
21
|
id: string,
|
|
22
22
|
label: string,
|
|
23
23
|
): ReactNode => {
|
|
24
|
-
|
|
25
|
-
const
|
|
24
|
+
// Only add description when open
|
|
25
|
+
const describedBy = isOpen ? id : undefined;
|
|
26
26
|
|
|
27
27
|
return useMemo(() => {
|
|
28
28
|
// Non-disabled element
|
|
29
|
-
if (
|
|
30
|
-
const
|
|
29
|
+
if (React.isValidElement(children) && children.props.disabled !== true && children.props.isDisabled !== true) {
|
|
30
|
+
const ref = mergeRefs((children as any).ref, setAnchorElement);
|
|
31
|
+
const props = { ...children.props, ref };
|
|
31
32
|
|
|
32
33
|
// Add current tooltip to the aria-describedby if the label is not already present
|
|
33
|
-
if (label !== props['aria-label']) {
|
|
34
|
-
props['aria-describedby'] = [props['aria-describedby'],
|
|
34
|
+
if (label !== props['aria-label'] && describedBy) {
|
|
35
|
+
props['aria-describedby'] = [props['aria-describedby'], describedBy].filter(Boolean).join(' ');
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
return cloneElement(
|
|
38
|
+
return cloneElement(children, props);
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
// Else add a wrapper around the children
|
|
41
41
|
return (
|
|
42
|
-
<div className="lumx-tooltip-anchor-wrapper" ref={
|
|
42
|
+
<div className="lumx-tooltip-anchor-wrapper" ref={setAnchorElement} aria-describedby={describedBy}>
|
|
43
43
|
{children}
|
|
44
44
|
</div>
|
|
45
45
|
);
|
|
46
|
-
}, [
|
|
46
|
+
}, [children, setAnchorElement, describedBy, label]);
|
|
47
47
|
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React, { useLayoutEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Helper component using useLayoutEffect to trigger a callback on before unmount.
|
|
5
|
+
*
|
|
6
|
+
* The callback must be wrapped in a React ref to avoid updating the `useLayoutEffect` before the un-mount
|
|
7
|
+
*/
|
|
8
|
+
export const OnBeforeUnmount = ({ callback }: { callback: React.RefObject<() => void> }) => {
|
|
9
|
+
useLayoutEffect(
|
|
10
|
+
() => {
|
|
11
|
+
return () => {
|
|
12
|
+
// On unmount
|
|
13
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
14
|
+
callback.current?.();
|
|
15
|
+
};
|
|
16
|
+
}, // eslint-disable-next-line react-hooks/exhaustive-deps
|
|
17
|
+
[],
|
|
18
|
+
);
|
|
19
|
+
return null;
|
|
20
|
+
};
|
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
/** Check if the focus is visible on the given element */
|
|
2
|
-
export const isFocusVisible = (element?: HTMLElement) =>
|
|
3
|
-
|
|
2
|
+
export const isFocusVisible = (element?: HTMLElement) => {
|
|
3
|
+
try {
|
|
4
|
+
return element?.matches?.(':focus-visible, [data-focus-visible-added]');
|
|
5
|
+
} catch (_ignored) {
|
|
6
|
+
// Can fail on non browser env
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
};
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import React, { useLayoutEffect } from 'react';
|
|
2
|
-
|
|
3
|
-
/** Small helper component using useLayoutEffect to trigger a callback on before unmount. */
|
|
4
|
-
const OnUnmount = ({ onBeforeUnmount }: { onBeforeUnmount: () => void }) => {
|
|
5
|
-
useLayoutEffect(() => onBeforeUnmount, [onBeforeUnmount]);
|
|
6
|
-
return null;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Provides a sentinel to inject the React tree that triggers the callback on before unmount.
|
|
11
|
-
*/
|
|
12
|
-
export function useBeforeUnmountSentinel(onBeforeUnmount?: () => void) {
|
|
13
|
-
return React.useMemo(() => {
|
|
14
|
-
if (!onBeforeUnmount) return undefined;
|
|
15
|
-
return <OnUnmount onBeforeUnmount={onBeforeUnmount} />;
|
|
16
|
-
}, [onBeforeUnmount]);
|
|
17
|
-
}
|