@moneyforward/mfui-components 4.3.1 → 4.6.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/dist/src/Accordion/Accordion.d.ts +21 -1
- package/dist/src/Accordion/Accordion.js +28 -4
- package/dist/src/Accordion/Accordion.types.d.ts +21 -0
- package/dist/src/Badge/Badge.d.ts +1 -1
- package/dist/src/Badge/Badge.js +4 -4
- package/dist/src/Badge/Badge.types.d.ts +3 -1
- package/dist/src/Breadcrumbs/Breadcrumbs.js +1 -1
- package/dist/src/Checkbox/Checkbox.js +7 -3
- package/dist/src/CheckboxCard/CheckboxCard.js +3 -1
- package/dist/src/DataTable/DataTable.js +9 -2
- package/dist/src/DataTable/DataTable.types.d.ts +17 -2
- package/dist/src/DataTable/DataTableCell/DataTableCell.js +21 -4
- package/dist/src/DataTable/DataTableCell/DataTableCell.types.d.ts +1 -1
- package/dist/src/DataTable/DataTableHeaderRow/DataTableHeaderRow.js +9 -5
- package/dist/src/DataTable/DataTableHeaderRow/DataTableHeaderRow.types.d.ts +19 -1
- package/dist/src/DataTable/DataTableRow/DataTableRow.js +106 -31
- package/dist/src/DataTable/DataTableRow/DataTableRow.types.d.ts +63 -12
- package/dist/src/DataTable/DataTableRowActionContext.d.ts +34 -0
- package/dist/src/DataTable/DataTableRowActionContext.js +8 -0
- package/dist/src/DateTimeSelection/DatePicker/DatePickerCalendar/DatePickerCalendar.js +7 -3
- package/dist/src/DateTimeSelection/DatePicker/DatePickerDateCell/index.d.ts +1 -1
- package/dist/src/DateTimeSelection/DatePicker/DatePickerDateCell/index.js +0 -1
- package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerPopover/DateRangePickerCalendar/DateRangePickerCalendar.js +1 -1
- package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerPopover/utilities/createDateRangePickerPopoverTestUtility.js +2 -2
- package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerProvider/DateRangePickerProvider.js +13 -7
- package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerTrigger/DateRangePickerTrigger.js +1 -1
- package/dist/src/DateTimeSelection/FilterDatePicker/FilterDatePicker.js +1 -1
- package/dist/src/DateTimeSelection/FilterMonthPicker/FilterMonthPicker.js +1 -1
- package/dist/src/DateTimeSelection/FilterMonthRangePicker/FilterMonthRangePicker.js +1 -1
- package/dist/src/DateTimeSelection/MonthPicker/MonthPickerPanel/MonthPickerPanel.js +1 -1
- package/dist/src/DateTimeSelection/MonthPicker/constants.d.ts +2 -2
- package/dist/src/DateTimeSelection/MonthPicker/constants.js +6 -6
- package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePickerMonthCell/MonthRangePickerMonthCell.js +1 -1
- package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePickerNavigation/MonthRangePickerNavigation.js +2 -2
- package/dist/src/DateTimeSelection/TimePicker/TimePicker.js +1 -1
- package/dist/src/DateTimeSelection/shared/BasePicker/constants.d.ts +2 -2
- package/dist/src/DateTimeSelection/shared/BasePicker/constants.js +10 -10
- package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerPopover/BaseRangePickerCalendar/BaseRangePickerCalendar.js +1 -1
- package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerPopover/constants.js +4 -4
- package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerProvider/BaseRangePickerProvider.js +13 -7
- package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerTrigger/BaseRangePickerTrigger.js +2 -2
- package/dist/src/DateTimeSelection/shared/CalendarGrid/CalendarGrid.js +2 -4
- package/dist/src/DateTimeSelection/shared/utilities/japaneseCalendar.js +2 -2
- package/dist/src/Dialog/Dialog.d.ts +1 -1
- package/dist/src/Dialog/Dialog.types.d.ts +1 -1
- package/dist/src/FileDropZone/FileDropZone.js +1 -1
- package/dist/src/FileDropZone/utils/fileHelpers.js +1 -1
- package/dist/src/MainNavigation/BaseMainNavigation.d.ts +1 -1
- package/dist/src/MainNavigation/BaseMainNavigation.js +3 -3
- package/dist/src/MainNavigation/MainNavigation.js +2 -1
- package/dist/src/MainNavigation/MainNavigation.types.d.ts +9 -0
- package/dist/src/MainNavigation/NarrowViewportMainNavigation.js +3 -3
- package/dist/src/MultipleSelectBox/MultipleSelectBox.js +27 -18
- package/dist/src/MultipleSelectBox/MultipleSelectBoxTrigger/MultipleSelectBoxTrigger.js +1 -1
- package/dist/src/MultipleSelectBox/hooks/useCreateOption.js +8 -4
- package/dist/src/MultipleSelectBox/hooks/useInitialFocusOnOptionPanelOpen.js +1 -1
- package/dist/src/MultipleSelectBox/hooks/useOptionKeyboardNavigation.js +9 -4
- package/dist/src/MultipleSelectBox/hooks/useSelectedValues.js +16 -50
- package/dist/src/NavigationList/NavigationListItem/NavigationListItem.d.ts +3 -0
- package/dist/src/NavigationList/NavigationListItem/NavigationListItem.js +6 -2
- package/dist/src/NavigationList/NavigationListItem/NavigationListItem.types.d.ts +2 -1
- package/dist/src/NumberBox/NumberBox.d.ts +5 -0
- package/dist/src/NumberBox/NumberBox.js +61 -6
- package/dist/src/NumberBox/NumberBox.types.d.ts +35 -0
- package/dist/src/PageHeader/PageHeader.types.d.ts +2 -0
- package/dist/src/Pagination/Pagination.js +4 -1
- package/dist/src/Popover/Popover.js +12 -4
- package/dist/src/RadioButtonCard/RadioButtonCard.js +3 -1
- package/dist/src/SelectBox/SelectBox.js +29 -21
- package/dist/src/SelectBox/hooks/useKeyboardNavigation.js +8 -2
- package/dist/src/SelectBox/utils/getFlatOptions.js +1 -1
- package/dist/src/SidePane/SidePane.js +87 -7
- package/dist/src/SidePane/SidePane.types.d.ts +39 -0
- package/dist/src/SplitView/hooks/useSplitViewPanelVisibility.js +5 -2
- package/dist/src/SubNavigation/SubNavigation.d.ts +10 -1
- package/dist/src/SubNavigation/SubNavigation.js +93 -21
- package/dist/src/SubNavigation/SubNavigation.types.d.ts +45 -7
- package/dist/src/Tabs/Tabs.js +8 -4
- package/dist/src/Tabs/hooks/useCalculatedSelectedTabId.js +7 -5
- package/dist/src/Tabs/hooks/useTabRegistry.js +5 -5
- package/dist/src/Toast/ToastContainer/ToastContainer.js +11 -2
- package/dist/src/ToggleSwitch/index.d.ts +1 -1
- package/dist/src/ToggleSwitch/index.js +0 -1
- package/dist/src/Tooltip/Tooltip.js +8 -3
- package/dist/src/Tooltip/hooks/useTooltipDisplayController.js +77 -88
- package/dist/src/utilities/dom/containsAcrossShadowRoots.d.ts +21 -0
- package/dist/src/utilities/dom/containsAcrossShadowRoots.js +33 -0
- package/dist/src/utilities/dom/getFocusableNodes.js +1 -3
- package/dist/src/utilities/dom/useFixedColumns.js +6 -8
- package/dist/src/utilities/dom/useFocusTrap.js +1 -1
- package/dist/src/utilities/dom/useTableEdgeCellMarkers.d.ts +56 -0
- package/dist/src/utilities/dom/useTableEdgeCellMarkers.js +223 -0
- package/dist/src/utilities/react/flattenChildren.d.ts +15 -0
- package/dist/src/utilities/react/flattenChildren.js +28 -0
- package/dist/src/utilities/state/useLatest.d.ts +1 -1
- package/dist/src/utilities/state/useLatest.js +8 -2
- package/dist/styled-system/recipes/badge-slot-recipe.d.ts +1 -1
- package/dist/styled-system/recipes/badge-slot-recipe.js +4 -0
- package/dist/styled-system/recipes/data-table-cell-slot-recipe.d.ts +1 -1
- package/dist/styled-system/recipes/data-table-cell-slot-recipe.js +8 -2
- package/dist/styled-system/recipes/data-table-row-slot-recipe.js +13 -6
- package/dist/styled-system/recipes/sub-navigation-slot-recipe.d.ts +1 -1
- package/dist/styled-system/recipes/sub-navigation-slot-recipe.js +12 -0
- package/dist/styles.css +175 -39
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +12 -12
- /package/dist/src/{MultipleSelectBox/utils → utilities/dom}/getDeepActiveElement.d.ts +0 -0
- /package/dist/src/{MultipleSelectBox/utils → utilities/dom}/getDeepActiveElement.js +0 -0
|
@@ -1,17 +1,28 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { DisclosureBasicCollapsed, Lock } from '@moneyforward/mfui-icons-react';
|
|
3
|
+
import { forwardRef, useId } from 'react';
|
|
3
4
|
import { cx } from '../../styled-system/css';
|
|
4
5
|
import { subNavigationSlotRecipe } from '../../styled-system/recipes';
|
|
5
6
|
import { FocusIndicator } from '../FocusIndicator';
|
|
6
7
|
import { Typography } from '../Typography';
|
|
7
8
|
/**
|
|
8
9
|
* This component is for the navigation links under the main navigation.
|
|
10
|
+
*
|
|
11
|
+
* The root element is a `<nav>` landmark. Name it with `aria-label` or `aria-labelledby` when the
|
|
12
|
+
* page has other navigation landmarks. No name is applied by default because the right name
|
|
13
|
+
* depends on what the navigation is for on each page.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* <SubNavigation navigationItems={items} aria-label="設定" />
|
|
18
|
+
* ```
|
|
9
19
|
*/
|
|
10
|
-
export
|
|
20
|
+
export const SubNavigation = forwardRef(({ orientation = 'vertical', navigationItems, customLinkComponent, renderNavigationLabel, headerSlot, className, ...rest }, ref) => {
|
|
11
21
|
const hasLabelIcon = navigationItems.some(({ labelIcon }) => !!labelIcon);
|
|
12
22
|
const classes = subNavigationSlotRecipe({ orientation, hasLabelIcon });
|
|
13
|
-
return (_jsxs("nav", { className: cx(classes.root, 'mfui-SubNavigation__root', className), children: [headerSlot ? (_jsx("div", { className: cx(classes.subNavigationHeader, 'mfui-SubNavigation__subNavigationHeader'), children: headerSlot })) : null, _jsx("ul", { className: cx(classes.list, 'mfui-SubNavigation__list'), children: navigationItems.map((navigationItem, index) => (_jsx("li", { className: cx(classes.listItem, 'mfui-SubNavigation__listItem'), children: renderNavigationItem(navigationItem, customLinkComponent, classes, 0, renderNavigationLabel) }, index))) })] }));
|
|
14
|
-
}
|
|
23
|
+
return (_jsxs("nav", { ...rest, ref: ref, className: cx(classes.root, 'mfui-SubNavigation__root', className), children: [headerSlot ? (_jsx("div", { className: cx(classes.subNavigationHeader, 'mfui-SubNavigation__subNavigationHeader'), children: headerSlot })) : null, _jsx("ul", { className: cx(classes.list, 'mfui-SubNavigation__list'), children: navigationItems.map((navigationItem, index) => (_jsx("li", { className: cx(classes.listItem, 'mfui-SubNavigation__listItem'), children: renderNavigationItem(navigationItem, customLinkComponent, classes, 0, renderNavigationLabel) }, index))) })] }));
|
|
24
|
+
});
|
|
25
|
+
SubNavigation.displayName = 'SubNavigation';
|
|
15
26
|
/**
|
|
16
27
|
* Render a navigation item based on the following conditions:
|
|
17
28
|
* - a link without children
|
|
@@ -19,6 +30,7 @@ export function SubNavigation({ orientation = 'vertical', navigationItems, custo
|
|
|
19
30
|
* - a disclosure button with children
|
|
20
31
|
*
|
|
21
32
|
* This function is splitted from the SubNavigation component to allow for recursive rendering for deep nesting.
|
|
33
|
+
* It is called inside loops, so it must not call hooks. Create them in the rendered components instead.
|
|
22
34
|
*
|
|
23
35
|
* @param navigationItem - The navigation item to render.
|
|
24
36
|
*
|
|
@@ -33,57 +45,117 @@ export function SubNavigation({ orientation = 'vertical', navigationItems, custo
|
|
|
33
45
|
* @returns The rendered navigation item.
|
|
34
46
|
*/
|
|
35
47
|
const renderNavigationItem = (navigationItem, customLinkComponent, classes, nestLevel, renderNavigationLabel) => {
|
|
36
|
-
|
|
37
|
-
if (children === undefined) {
|
|
48
|
+
if (navigationItem.children === undefined) {
|
|
38
49
|
return (_jsx(NavigationLink, { navigationItem: navigationItem, customLinkComponent: customLinkComponent, classes: classes, renderNavigationLabel: renderNavigationLabel }));
|
|
39
50
|
}
|
|
40
|
-
|
|
51
|
+
const nestedNavigationList = (_jsx(NestedNavigationList, { parentNavigationItem: navigationItem, customLinkComponent: customLinkComponent, classes: classes, nestLevel: nestLevel + 1, renderNavigationLabel: renderNavigationLabel }));
|
|
52
|
+
if (navigationItem.href) {
|
|
41
53
|
// The the parent item will be rendered as a link with separated disclosure icon button.
|
|
42
|
-
return (_jsxs(_Fragment, { children: [_jsx(NavigationLink, { navigationItem: navigationItem, customLinkComponent: customLinkComponent, classes: classes, renderNavigationLabel: renderNavigationLabel }),
|
|
54
|
+
return (_jsxs(_Fragment, { children: [_jsx(NavigationLink, { navigationItem: navigationItem, customLinkComponent: customLinkComponent, classes: classes, renderNavigationLabel: renderNavigationLabel }), nestedNavigationList] }));
|
|
43
55
|
}
|
|
44
56
|
// The the parent item will be rendered as a disclosure button with an icon.
|
|
45
|
-
return
|
|
57
|
+
return nestedNavigationList;
|
|
46
58
|
};
|
|
47
59
|
/**
|
|
48
|
-
*
|
|
60
|
+
* Create the IDs only when the item has a description.
|
|
61
|
+
* Without a description, the item keeps its accessible name from its content.
|
|
62
|
+
*
|
|
63
|
+
* @param hasDescription - Whether the item has a description.
|
|
64
|
+
*
|
|
65
|
+
* @returns The IDs, or `undefined` when the item has no description.
|
|
66
|
+
*/
|
|
67
|
+
function useLabelIds(hasDescription) {
|
|
68
|
+
const id = useId();
|
|
69
|
+
if (!hasDescription)
|
|
70
|
+
return undefined;
|
|
71
|
+
return { label: `${id}-label`, status: `${id}-status`, description: `${id}-description` };
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Get the ARIA attributes for the element that contains the NavigationLabelGroup.
|
|
75
|
+
*
|
|
76
|
+
* @param labelIds - The IDs from `useLabelIds`.
|
|
77
|
+
*
|
|
78
|
+
* @param hasStatus - Whether the status is displayed.
|
|
79
|
+
*
|
|
80
|
+
* @returns The ARIA attributes. Empty when the item has no description.
|
|
81
|
+
*/
|
|
82
|
+
function getLabelAriaProps(labelIds, hasStatus) {
|
|
83
|
+
if (!labelIds)
|
|
84
|
+
return {};
|
|
85
|
+
return {
|
|
86
|
+
'aria-labelledby': hasStatus ? `${labelIds.label} ${labelIds.status}` : labelIds.label,
|
|
87
|
+
'aria-describedby': labelIds.description,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* This is a group of a text label, an optional description, an optional icon, and a status.
|
|
49
92
|
* This is used for all navigation links and parent items for disclosure buttons in SubNavigation.
|
|
50
93
|
*/
|
|
51
|
-
function NavigationLabelGroup({ label, labelIcon, locked, lockIconProps, statusSlot, classes, renderNavigationLabel, }) {
|
|
94
|
+
function NavigationLabelGroup({ label, description, labelIcon, locked, lockIconProps, statusSlot, labelIds, classes, renderNavigationLabel, }) {
|
|
52
95
|
// If locked=true, display <Lock /> icon in preference to any other status of the statusSlot option.
|
|
53
96
|
const renderStatus = () => {
|
|
54
97
|
if (locked) {
|
|
55
|
-
return (_jsx("
|
|
98
|
+
return (_jsx("span", { id: labelIds?.status, className: cx(classes.statusSlot, 'mfui-SubNavigation__statusSlot'), children: _jsx(Lock, { role: "img", "aria-label": lockIconProps?.['aria-label'] ?? 'ロックされています' }) }));
|
|
56
99
|
}
|
|
57
100
|
if (statusSlot) {
|
|
58
|
-
return _jsx("
|
|
101
|
+
return (_jsx("span", { id: labelIds?.status, className: cx(classes.statusSlot, 'mfui-SubNavigation__statusSlot'), children: statusSlot }));
|
|
59
102
|
}
|
|
60
103
|
return null;
|
|
61
104
|
};
|
|
62
|
-
|
|
105
|
+
const labelText = renderNavigationLabel ? (renderNavigationLabel(label)) : (_jsx(Typography, { variant: "strongControlLabel", className: cx(classes.label, 'mfui-SubNavigation__label'), children: label }));
|
|
106
|
+
return (_jsxs(_Fragment, { children: [_jsxs("span", { className: cx(classes.labelContainer, 'mfui-SubNavigation__labelContainer'), children: [labelIcon ? (_jsx("span", { "aria-hidden": "true", className: cx(classes.labelIcon, 'mfui-SubNavigation__labelIcon'), children: labelIcon })) : null, _jsxs("span", { className: cx(classes.labelContent, 'mfui-SubNavigation__labelContent'), children: [labelIds ? _jsx("span", { id: labelIds.label, children: labelText }) : labelText, description ? (_jsx(Typography, { variant: "helpMessage", id: labelIds?.description, "data-mfui-content": "sub-navigation-description", className: cx(classes.description, 'mfui-SubNavigation__description'), children: description })) : null] })] }), renderStatus()] }));
|
|
63
107
|
}
|
|
64
108
|
/**
|
|
65
109
|
* This is a link that has NavigationLabelGroup.
|
|
66
110
|
* This is used for all navigation links in SubNavigation.
|
|
67
111
|
*/
|
|
68
112
|
function NavigationLink({ navigationItem, customLinkComponent, classes, renderNavigationLabel, }) {
|
|
69
|
-
const { label, href, isCurrent, isExternal, labelIcon, statusSlot, locked, lockIconProps } = navigationItem;
|
|
113
|
+
const { label, description, href, isCurrent, isExternal, labelIcon, statusSlot, locked, lockIconProps } = navigationItem;
|
|
114
|
+
const labelIds = useLabelIds(!!description);
|
|
115
|
+
const labelAriaProps = getLabelAriaProps(labelIds, !!locked || !!statusSlot);
|
|
116
|
+
const labelGroup = (_jsx(NavigationLabelGroup, { label,
|
|
117
|
+
description,
|
|
118
|
+
labelIcon,
|
|
119
|
+
locked,
|
|
120
|
+
lockIconProps,
|
|
121
|
+
statusSlot,
|
|
122
|
+
labelIds,
|
|
123
|
+
classes,
|
|
124
|
+
renderNavigationLabel }));
|
|
70
125
|
if (navigationItem.children === undefined && navigationItem.onClick) {
|
|
71
126
|
const { onClick } = navigationItem;
|
|
72
|
-
return (_jsx(FocusIndicator, { children: _jsx("button", { type: "button", className: cx(classes.link, 'mfui-SubNavigation__link'), "aria-current": isCurrent ? 'page' : undefined, onClick: () => {
|
|
127
|
+
return (_jsx(FocusIndicator, { children: _jsx("button", { type: "button", className: cx(classes.link, 'mfui-SubNavigation__link'), "aria-current": isCurrent ? 'page' : undefined, ...labelAriaProps, onClick: () => {
|
|
73
128
|
onClick(navigationItem.href);
|
|
74
|
-
}, children:
|
|
129
|
+
}, children: labelGroup }) }));
|
|
75
130
|
}
|
|
76
131
|
if (customLinkComponent && !isExternal) {
|
|
77
132
|
const Tag = customLinkComponent;
|
|
78
|
-
return (_jsx(FocusIndicator, { children: _jsx(Tag, { href: href, className: cx(classes.link, 'mfui-SubNavigation__link'), "aria-current": isCurrent ? 'page' : undefined, children:
|
|
133
|
+
return (_jsx(FocusIndicator, { children: _jsx(Tag, { href: href, className: cx(classes.link, 'mfui-SubNavigation__link'), "aria-current": isCurrent ? 'page' : undefined, ...labelAriaProps, children: labelGroup }) }));
|
|
79
134
|
}
|
|
80
|
-
return (_jsx(FocusIndicator, { children: _jsx("a", { href: href, target: isExternal ? '_blank' : undefined, className: cx(classes.link, 'mfui-SubNavigation__link'), "aria-current": isCurrent ? 'page' : undefined, rel: "noreferrer", children:
|
|
135
|
+
return (_jsx(FocusIndicator, { children: _jsx("a", { href: href, target: isExternal ? '_blank' : undefined, className: cx(classes.link, 'mfui-SubNavigation__link'), "aria-current": isCurrent ? 'page' : undefined, rel: "noreferrer", ...labelAriaProps, children: labelGroup }) }));
|
|
81
136
|
}
|
|
82
137
|
/**
|
|
83
138
|
* This is a details element that contains a list of nested navigation links.
|
|
139
|
+
*
|
|
140
|
+
* - When the parent item has `href`, the summary is only the disclosure icon button next to the parent link.
|
|
141
|
+
* - Otherwise, the summary is the disclosure button with the label of the parent item.
|
|
84
142
|
*/
|
|
85
|
-
function NestedNavigationList({
|
|
143
|
+
function NestedNavigationList({ parentNavigationItem, customLinkComponent, classes, nestLevel, renderNavigationLabel, }) {
|
|
144
|
+
const { label, description, href, labelIcon, statusSlot, locked, lockIconProps, children } = parentNavigationItem;
|
|
145
|
+
// With href, the description belongs to the parent link, not to the disclosure icon button.
|
|
146
|
+
const labelIds = useLabelIds(!href && !!description);
|
|
147
|
+
const labelAriaProps = getLabelAriaProps(labelIds, !!locked || !!statusSlot);
|
|
86
148
|
// Check whether all descendants have isCurrent=true by calling itself recursively.
|
|
87
|
-
const isCurrentPathInChildren = (children) => children?.some(({ isCurrent, children }) => isCurrent || (children && isCurrentPathInChildren(children)))
|
|
88
|
-
|
|
149
|
+
const isCurrentPathInChildren = (children) => children?.some(({ isCurrent, children }) => !!isCurrent || !!(children && isCurrentPathInChildren(children))) ??
|
|
150
|
+
false;
|
|
151
|
+
const summarySlot = href ? (_jsx("span", { "data-mfui-content": "sub-navigation-disclosure-icon", className: cx(classes.disclosureIcon, 'mfui-SubNavigation__disclosureIcon'), children: _jsx(DisclosureBasicCollapsed, { role: "img", "aria-label": label }) })) : (_jsxs(_Fragment, { children: [_jsx(NavigationLabelGroup, { label,
|
|
152
|
+
description,
|
|
153
|
+
labelIcon,
|
|
154
|
+
locked,
|
|
155
|
+
lockIconProps,
|
|
156
|
+
statusSlot,
|
|
157
|
+
labelIds,
|
|
158
|
+
classes,
|
|
159
|
+
renderNavigationLabel }), _jsx("span", { "data-mfui-content": "sub-navigation-disclosure-icon", className: cx(classes.disclosureIcon, 'mfui-SubNavigation__disclosureIcon'), children: _jsx(DisclosureBasicCollapsed, { "aria-hidden": "true" }) })] }));
|
|
160
|
+
return (_jsxs("details", { open: isCurrentPathInChildren(children), className: cx(classes.details, 'mfui-SubNavigation__details'), children: [_jsx(FocusIndicator, { children: _jsx("summary", { className: cx(classes.summary, 'mfui-SubNavigation__summary'), ...labelAriaProps, children: summarySlot }) }), _jsx("ul", { className: cx(classes.list, 'mfui-SubNavigation__list'), children: children.map((nestedNavigationItem, index) => (_jsx("li", { className: cx(classes.nestedListItem, 'mfui-SubNavigation__nestedListItem'), "data-mfui-nest-level": nestLevel, children: nestedNavigationItem.children === undefined ? (_jsx(NavigationLink, { navigationItem: nestedNavigationItem, customLinkComponent: customLinkComponent, classes: classes, renderNavigationLabel: renderNavigationLabel })) : (renderNavigationItem(nestedNavigationItem, customLinkComponent, classes, nestLevel, renderNavigationLabel)) }, index))) })] }));
|
|
89
161
|
}
|
|
@@ -33,6 +33,32 @@ type HorizontalSubNavigationProps = BaseSubNavigationProps & {
|
|
|
33
33
|
* Common properties for both vertical and horizontal SubNavigation.
|
|
34
34
|
*/
|
|
35
35
|
type BaseSubNavigationProps = {
|
|
36
|
+
/**
|
|
37
|
+
* The accessible name of the `<nav>` landmark.
|
|
38
|
+
*
|
|
39
|
+
* Set this when the page has other navigation landmarks (e.g. `MainNavigation`, `Breadcrumbs`).
|
|
40
|
+
* Describe this page's navigation (e.g. `"設定"`). Avoid including the role name;
|
|
41
|
+
* assistive technology already announces it.
|
|
42
|
+
*/
|
|
43
|
+
'aria-label'?: string;
|
|
44
|
+
/**
|
|
45
|
+
* The ID of the element that names the `<nav>` landmark.
|
|
46
|
+
* Use this instead of `aria-label` when a visible heading already names the navigation.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```tsx
|
|
50
|
+
* function SettingsNavigation() {
|
|
51
|
+
* const headingId = useId();
|
|
52
|
+
* return (
|
|
53
|
+
* <>
|
|
54
|
+
* <h2 id={headingId}>設定</h2>
|
|
55
|
+
* <SubNavigation navigationItems={items} aria-labelledby={headingId} />
|
|
56
|
+
* </>
|
|
57
|
+
* );
|
|
58
|
+
* }
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
'aria-labelledby'?: string;
|
|
36
62
|
/**
|
|
37
63
|
* The component to use as the link element instead of the general anchor element `<a>`.
|
|
38
64
|
* The custom component must extend the HTMLAnchorElement interface.
|
|
@@ -50,12 +76,12 @@ type BaseSubNavigationProps = {
|
|
|
50
76
|
customLinkComponent?: ElementType;
|
|
51
77
|
/**
|
|
52
78
|
* Custom render function for each navigation item's label.
|
|
53
|
-
* Receives the item's raw `label` string and returns a ReactNode
|
|
54
|
-
* the default `<Typography variant="strongControlLabel">`
|
|
55
|
-
* When omitted, the label string is rendered
|
|
79
|
+
* Receives the item's raw `label` string and returns a ReactNode that replaces
|
|
80
|
+
* the default `<Typography variant="strongControlLabel">` label.
|
|
81
|
+
* When omitted, the label string is rendered with the default label style.
|
|
56
82
|
*
|
|
57
|
-
* Use this to truncate long labels or otherwise customize how labels are displayed
|
|
58
|
-
*
|
|
83
|
+
* Use this to truncate long labels or otherwise customize how labels are displayed.
|
|
84
|
+
* It only affects the label, not the item's `description`.
|
|
59
85
|
*
|
|
60
86
|
* To show a tooltip on both hover and focus, wrap the entire link with a `Tooltip`
|
|
61
87
|
* via `customLinkComponent` instead — placing a `Tooltip` inside `renderNavigationLabel`
|
|
@@ -64,7 +90,7 @@ type BaseSubNavigationProps = {
|
|
|
64
90
|
*
|
|
65
91
|
* @param label - The label string of the navigation item.
|
|
66
92
|
*
|
|
67
|
-
* @returns ReactNode - Custom content rendered
|
|
93
|
+
* @returns ReactNode - Custom content rendered in place of the default label.
|
|
68
94
|
*
|
|
69
95
|
* @example
|
|
70
96
|
* ```tsx
|
|
@@ -109,7 +135,7 @@ type BaseSubNavigationProps = {
|
|
|
109
135
|
* For overriding styles if you need.
|
|
110
136
|
*/
|
|
111
137
|
className?: string;
|
|
112
|
-
}
|
|
138
|
+
} & Omit<ComponentPropsWithoutRef<'nav'>, 'children' | 'className' | 'aria-label' | 'aria-labelledby'>;
|
|
113
139
|
/**
|
|
114
140
|
* The union that discriminates whether each navigation item has children.
|
|
115
141
|
*/
|
|
@@ -158,6 +184,18 @@ type BaseNavigationItem = {
|
|
|
158
184
|
* The text label briefly describing the linked page.
|
|
159
185
|
*/
|
|
160
186
|
label: string;
|
|
187
|
+
/**
|
|
188
|
+
* A short supplementary text displayed below the text label.
|
|
189
|
+
*
|
|
190
|
+
* Screen readers announce it as the item's description, after the label and the status.
|
|
191
|
+
* Only plain text is accepted because the item itself is a link or a button.
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* ```tsx
|
|
195
|
+
* <SubNavigation navigationItems={[{ label: '請求書', description: '作成・送付', href: '/invoices' }]} />
|
|
196
|
+
* ```
|
|
197
|
+
*/
|
|
198
|
+
description?: string;
|
|
161
199
|
/**
|
|
162
200
|
* Whether the navigation item is the current page.
|
|
163
201
|
*
|
package/dist/src/Tabs/Tabs.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { useMemo,
|
|
3
|
+
import { useMemo, useState } from 'react';
|
|
4
4
|
import { tabsSlotRecipe } from '../../styled-system/recipes';
|
|
5
5
|
import { useCalculatedSelectedTabId } from './hooks/useCalculatedSelectedTabId';
|
|
6
6
|
import { useTabRegistry } from './hooks/useTabRegistry';
|
|
@@ -13,7 +13,10 @@ import { cx } from '../../styled-system/css';
|
|
|
13
13
|
function Base({ selectedTabId, onSelectedTabChange, targetDOMNode, enableAutoUnmount = true, children, className, style, }) {
|
|
14
14
|
const classNames = tabsSlotRecipe();
|
|
15
15
|
const [shouldShowMenu, setShouldShowMenu] = useState(false);
|
|
16
|
-
|
|
16
|
+
// State (not a ref) so `getTargetDomNode` below can be given the wrapper element
|
|
17
|
+
// without reading a ref's `.current` during render (unsafe: a ref update never
|
|
18
|
+
// triggers the re-render needed to pass a fresh value into context).
|
|
19
|
+
const [wrapperElement, setWrapperElement] = useState(null);
|
|
17
20
|
const { tabs, registerTab, updateTab, unregisterTab } = useTabRegistry();
|
|
18
21
|
const { calculatedSelectedTabId, setCalculatedSelectedTab } = useCalculatedSelectedTabId({
|
|
19
22
|
selectedTabId,
|
|
@@ -26,7 +29,7 @@ function Base({ selectedTabId, onSelectedTabChange, targetDOMNode, enableAutoUnm
|
|
|
26
29
|
tabs,
|
|
27
30
|
shouldShowMenu,
|
|
28
31
|
setShouldShowMenu,
|
|
29
|
-
targetDOMNode: getTargetDomNode(targetDOMNode,
|
|
32
|
+
targetDOMNode: getTargetDomNode(targetDOMNode, wrapperElement),
|
|
30
33
|
registerTab,
|
|
31
34
|
updateTab,
|
|
32
35
|
unregisterTab,
|
|
@@ -38,12 +41,13 @@ function Base({ selectedTabId, onSelectedTabChange, targetDOMNode, enableAutoUnm
|
|
|
38
41
|
shouldShowMenu,
|
|
39
42
|
setShouldShowMenu,
|
|
40
43
|
targetDOMNode,
|
|
44
|
+
wrapperElement,
|
|
41
45
|
registerTab,
|
|
42
46
|
updateTab,
|
|
43
47
|
unregisterTab,
|
|
44
48
|
enableAutoUnmount,
|
|
45
49
|
]);
|
|
46
|
-
return (_jsx(TabContext.Provider, { value: contextValue, children: _jsx("div", { ref:
|
|
50
|
+
return (_jsx(TabContext.Provider, { value: contextValue, children: _jsx("div", { ref: setWrapperElement, className: cx(classNames.root, 'mfui-Tabs__root', className), style: style, children: children }) }));
|
|
47
51
|
}
|
|
48
52
|
/**
|
|
49
53
|
* The Tabs component manages the tabs, tab lists, and tab panels within a tab section.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useState } from 'react';
|
|
2
2
|
/**
|
|
3
3
|
* Custom hook to manage the selected tab ID based on provided options.
|
|
4
4
|
* Automatically updates the selected tab ID when the `selectedTabId` prop changes.
|
|
@@ -12,11 +12,13 @@ import { useEffect, useState } from 'react';
|
|
|
12
12
|
export function useCalculatedSelectedTabId({ selectedTabId, onSelectedTabChange, tabs, }) {
|
|
13
13
|
// implement
|
|
14
14
|
const [calculatedSelectedTabId, setCalculatedSelectedTabId] = useState(selectedTabId);
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
|
|
15
|
+
// Adjusted during render (React's documented pattern for resyncing state to a changed
|
|
16
|
+
// prop) rather than in an effect, so the UI never flashes the stale tab for one frame.
|
|
17
|
+
const [prevSelectedTabId, setPrevSelectedTabId] = useState(selectedTabId);
|
|
18
|
+
if (selectedTabId !== prevSelectedTabId) {
|
|
19
|
+
setPrevSelectedTabId(selectedTabId);
|
|
18
20
|
setCalculatedSelectedTabId(selectedTabId);
|
|
19
|
-
}
|
|
21
|
+
}
|
|
20
22
|
return {
|
|
21
23
|
// If still nothing, we pick the first tabId from the tab list
|
|
22
24
|
calculatedSelectedTabId: calculatedSelectedTabId ?? tabs[0]?.id,
|
|
@@ -44,14 +44,14 @@ function areTabListsEqual(a, b) {
|
|
|
44
44
|
});
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
|
-
*
|
|
47
|
+
* Returns the entries ordered by the position their elements appear in the document.
|
|
48
48
|
* Entries whose element is not in the document yet keep their relative order, because
|
|
49
|
-
* `Array#
|
|
49
|
+
* `Array#toSorted` is stable and the comparator reports them as equal.
|
|
50
50
|
*
|
|
51
|
-
* @param entries - The registered entries
|
|
51
|
+
* @param entries - The registered entries to order.
|
|
52
52
|
*/
|
|
53
53
|
function sortByDocumentPosition(entries) {
|
|
54
|
-
entries.
|
|
54
|
+
return entries.toSorted((a, b) => {
|
|
55
55
|
const aElement = a.elementRef.current;
|
|
56
56
|
const bElement = b.elementRef.current;
|
|
57
57
|
if (!aElement?.isConnected || !bElement?.isConnected)
|
|
@@ -81,7 +81,7 @@ export function useTabRegistry() {
|
|
|
81
81
|
const entriesRef = useRef([]);
|
|
82
82
|
const { registerTab, updateTab, unregisterTab } = useMemo(() => {
|
|
83
83
|
const publish = () => {
|
|
84
|
-
sortByDocumentPosition(entriesRef.current);
|
|
84
|
+
entriesRef.current = sortByDocumentPosition(entriesRef.current);
|
|
85
85
|
const nextTabs = entriesRef.current.map((entry) => entry.props);
|
|
86
86
|
setTabs((previousTabs) => (areTabListsEqual(previousTabs, nextTabs) ? previousTabs : nextTabs));
|
|
87
87
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
3
|
+
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
4
4
|
import { Toast } from '../Toast';
|
|
5
5
|
import { toastSlotRecipe } from '../../../styled-system/recipes';
|
|
6
6
|
import { cx } from '../../../styled-system/css';
|
|
@@ -34,6 +34,15 @@ export function ToastContainer({ items, onItemClose }) {
|
|
|
34
34
|
const [containerHeight, setContainerHeight] = useState(0);
|
|
35
35
|
const [activeElement, setActiveElement] = useState(null);
|
|
36
36
|
const [isMobile, setIsMobile] = useState(false);
|
|
37
|
+
// Mirrors `firstItemRef.current` into state so `TargetDomNodeProvider` below can be
|
|
38
|
+
// given it directly, instead of reading a ref's `.current` during render (unsafe: a
|
|
39
|
+
// ref update never triggers the re-render needed to pass a fresh value to context
|
|
40
|
+
// consumers).
|
|
41
|
+
const [firstItemElement, setFirstItemElement] = useState(null);
|
|
42
|
+
const setFirstItemRef = useCallback((node) => {
|
|
43
|
+
firstItemRef.current = node;
|
|
44
|
+
setFirstItemElement(node);
|
|
45
|
+
}, []);
|
|
37
46
|
// Detect viewport width for responsive CONTAINER_GAP
|
|
38
47
|
useLayoutEffect(() => {
|
|
39
48
|
const checkViewportWidth = () => {
|
|
@@ -114,7 +123,7 @@ export function ToastContainer({ items, onItemClose }) {
|
|
|
114
123
|
};
|
|
115
124
|
}, [items, activeElement, onItemClose]);
|
|
116
125
|
const containerGap = isMobile ? CONTAINER_GAP_MOBILE : CONTAINER_GAP_DESKTOP;
|
|
117
|
-
return (_jsx(TargetDomNodeProvider, { targetDomNode:
|
|
126
|
+
return (_jsx(TargetDomNodeProvider, { targetDomNode: firstItemElement, children: _jsx("div", { style: { height: containerHeight === 0 ? 0 : `calc(${containerHeight.toString()}px + ${containerGap})` }, className: cx(classes.container, 'mfui-ToastContainer__container'), children: items.map(({ key, onClose, duration, ...props }, index) => (_jsx(Toast, { ref: [setFirstItemRef, secondItemRef][index] ?? null, ...props, style: {
|
|
118
127
|
zIndex: 1000 - index,
|
|
119
128
|
}, activeElement: activeElement,
|
|
120
129
|
// If index is 0, set the duration to the first item. Otherwise, the duration will be `undefined`.
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export * from './ToggleSwitch';
|
|
2
|
-
export * from './ToggleSwitch.types';
|
|
2
|
+
export type * from './ToggleSwitch.types';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { forwardRef, useId, useRef, isValidElement, cloneElement, useEffect, useMemo } from 'react';
|
|
3
|
+
import { forwardRef, useId, useRef, isValidElement, cloneElement, useEffect, useMemo, useState } from 'react';
|
|
4
4
|
import { getOverflowAncestors } from '@floating-ui/react-dom';
|
|
5
5
|
import { cx } from '../../styled-system/css';
|
|
6
6
|
import { tooltipSlotRecipe } from '../../styled-system/recipes';
|
|
@@ -52,9 +52,14 @@ export const Tooltip = forwardRef(({ children, open, onOpenStateChanged, content
|
|
|
52
52
|
// Allow consumers to control the content id so an external element (e.g. a form field)
|
|
53
53
|
// can reference the single accessible copy of the content via its own aria-describedby.
|
|
54
54
|
const tooltipId = contentProps?.id ?? generatedId;
|
|
55
|
+
// Mirrors `rootRef.current` into state so it can be read below without reading a
|
|
56
|
+
// ref's `.current` during render (unsafe: a ref update never triggers the re-render
|
|
57
|
+
// needed to pass a fresh value onward). `rootRef` itself is kept for the effects and
|
|
58
|
+
// `useOnClickOutside` call further down, which read it safely outside of render.
|
|
59
|
+
const [rootElement, setRootElement] = useState(null);
|
|
55
60
|
// Use automatic target resolution matching Portal.tsx pattern
|
|
56
61
|
// Resolution order: explicit prop → parent context → document.body fallback
|
|
57
|
-
const targetElement = useAutomaticTargetDomNode(targetDOMNode,
|
|
62
|
+
const targetElement = useAutomaticTargetDomNode(targetDOMNode, rootElement);
|
|
58
63
|
const { open: calculatedOpen, rootAttributes, tooltipAttributes, closeImmediate, closeWithManager, } = useTooltipDisplayController({
|
|
59
64
|
trigger,
|
|
60
65
|
wrapperProps,
|
|
@@ -89,7 +94,7 @@ export const Tooltip = forwardRef(({ children, open, onOpenStateChanged, content
|
|
|
89
94
|
ignoreRefs: [tooltipRef],
|
|
90
95
|
disabled: !calculatedOpen || !hasClickTrigger || open !== undefined,
|
|
91
96
|
});
|
|
92
|
-
const triggerRef = mergeRefs(setTriggerReference, rootRef, ref);
|
|
97
|
+
const triggerRef = mergeRefs(setTriggerReference, rootRef, setRootElement, ref);
|
|
93
98
|
const classes = tooltipSlotRecipe();
|
|
94
99
|
const ariaDescribedBy = !hideFromScreenReader && content != null ? tooltipId : undefined;
|
|
95
100
|
return (_jsxs(_Fragment, { children: [_jsx("div", { ref: triggerRef, ...wrapperProps, className: cx(classes.root, 'mfui-Tooltip__root', className), ...rootAttributes, children: isValidElement(children) ? (cloneElement(children, ariaDescribedBy ? { 'aria-describedby': ariaDescribedBy } : undefined)) : (_jsx("span", { ...(ariaDescribedBy ? { 'aria-describedby': ariaDescribedBy } : undefined), children: children })) }), !calculatedOpen && !hideFromScreenReader && content != null && (_jsx(Portal, { targetDOMNode: targetElement, children: _jsx("div", { "data-mfui-tooltip-hidden": true, id: tooltipId, style: {
|
|
@@ -79,98 +79,87 @@ export function useTooltipDisplayController({ trigger, wrapperProps, tooltipOpen
|
|
|
79
79
|
return updatedWrapperProps;
|
|
80
80
|
if (tooltipOpen)
|
|
81
81
|
return updatedWrapperProps;
|
|
82
|
-
Object.assign
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
82
|
+
// Assigned directly (not via `Object.assign`) so the compiler can see these
|
|
83
|
+
// ref-closing handlers are only ever stored as props, never invoked here.
|
|
84
|
+
updatedWrapperProps.onMouseDown = (event) => {
|
|
85
|
+
isMouseTriggered.current = true;
|
|
86
|
+
wrapperProps.onMouseDown?.(event);
|
|
87
|
+
};
|
|
88
|
+
updatedWrapperProps.onMouseUp = (event) => {
|
|
89
|
+
isMouseTriggered.current = false;
|
|
90
|
+
wrapperProps.onMouseUp?.(event);
|
|
91
|
+
};
|
|
92
|
+
updatedWrapperProps.onKeyDown = (event) => {
|
|
93
|
+
if (event.key === 'Enter' && open) {
|
|
94
|
+
closeWithManager();
|
|
95
|
+
}
|
|
96
|
+
wrapperProps.onKeyDown?.(event);
|
|
97
|
+
};
|
|
98
98
|
if (triggers.includes('hover')) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
},
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
if (triggers.includes('click')) {
|
|
125
|
-
Object.assign(updatedWrapperProps, {
|
|
126
|
-
onClick: (event) => {
|
|
127
|
-
if (!isEventFromTrigger(event))
|
|
128
|
-
return;
|
|
129
|
-
if (!open) {
|
|
130
|
-
triggerSource.current = 'click';
|
|
131
|
-
openWithManager('immediate');
|
|
132
|
-
}
|
|
133
|
-
else {
|
|
134
|
-
closeWithManager();
|
|
135
|
-
}
|
|
136
|
-
wrapperProps.onClick?.(event);
|
|
137
|
-
},
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
else {
|
|
141
|
-
// When click is not a trigger, clicking should close the tooltip if it's open
|
|
142
|
-
Object.assign(updatedWrapperProps, {
|
|
143
|
-
onClick: (event) => {
|
|
144
|
-
if (!isEventFromTrigger(event))
|
|
145
|
-
return;
|
|
146
|
-
if (open) {
|
|
147
|
-
closeWithManager();
|
|
148
|
-
}
|
|
149
|
-
wrapperProps.onClick?.(event);
|
|
150
|
-
},
|
|
151
|
-
});
|
|
99
|
+
updatedWrapperProps.onMouseEnter = (event) => {
|
|
100
|
+
if (!isEventFromTrigger(event))
|
|
101
|
+
return;
|
|
102
|
+
triggerSource.current = 'hover';
|
|
103
|
+
isHoveringTrigger.current = true;
|
|
104
|
+
cancelClose(); // Cancel any pending close
|
|
105
|
+
openWithManager('delayed');
|
|
106
|
+
wrapperProps.onMouseEnter?.(event);
|
|
107
|
+
};
|
|
108
|
+
updatedWrapperProps.onMouseLeave = (event) => {
|
|
109
|
+
if (triggerSource.current !== 'hover')
|
|
110
|
+
return;
|
|
111
|
+
if (!isEventFromTrigger(event))
|
|
112
|
+
return;
|
|
113
|
+
isHoveringTrigger.current = false;
|
|
114
|
+
// Only start close timer if also not hovering tooltip
|
|
115
|
+
// Use queueMicrotask to avoid race conditions with rapid mouse events
|
|
116
|
+
queueMicrotask(() => {
|
|
117
|
+
closeWithDelayIfNotHovering();
|
|
118
|
+
});
|
|
119
|
+
wrapperProps.onMouseLeave?.(event);
|
|
120
|
+
};
|
|
152
121
|
}
|
|
153
|
-
if
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
return;
|
|
161
|
-
triggerSource.current = 'focus';
|
|
122
|
+
// When click is not a trigger, clicking should still close the tooltip if it's open.
|
|
123
|
+
updatedWrapperProps.onClick = triggers.includes('click')
|
|
124
|
+
? (event) => {
|
|
125
|
+
if (!isEventFromTrigger(event))
|
|
126
|
+
return;
|
|
127
|
+
if (!open) {
|
|
128
|
+
triggerSource.current = 'click';
|
|
162
129
|
openWithManager('immediate');
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
onBlur: (event) => {
|
|
166
|
-
if (triggerSource.current !== 'focus')
|
|
167
|
-
return;
|
|
168
|
-
if (!isEventFromTrigger(event))
|
|
169
|
-
return;
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
170
132
|
closeWithManager();
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
133
|
+
}
|
|
134
|
+
wrapperProps.onClick?.(event);
|
|
135
|
+
}
|
|
136
|
+
: (event) => {
|
|
137
|
+
if (!isEventFromTrigger(event))
|
|
138
|
+
return;
|
|
139
|
+
if (open) {
|
|
140
|
+
closeWithManager();
|
|
141
|
+
}
|
|
142
|
+
wrapperProps.onClick?.(event);
|
|
143
|
+
};
|
|
144
|
+
if (triggers.includes('focus')) {
|
|
145
|
+
updatedWrapperProps.onFocus = (event) => {
|
|
146
|
+
if (!isEventFromTrigger(event))
|
|
147
|
+
return;
|
|
148
|
+
// onFocus event is triggered before onClick event when the mouse is clicked, so we need to skip it
|
|
149
|
+
if (isMouseTriggered.current)
|
|
150
|
+
return;
|
|
151
|
+
triggerSource.current = 'focus';
|
|
152
|
+
openWithManager('immediate');
|
|
153
|
+
wrapperProps.onFocus?.(event);
|
|
154
|
+
};
|
|
155
|
+
updatedWrapperProps.onBlur = (event) => {
|
|
156
|
+
if (triggerSource.current !== 'focus')
|
|
157
|
+
return;
|
|
158
|
+
if (!isEventFromTrigger(event))
|
|
159
|
+
return;
|
|
160
|
+
closeWithManager();
|
|
161
|
+
wrapperProps.onBlur?.(event);
|
|
162
|
+
};
|
|
174
163
|
}
|
|
175
164
|
return updatedWrapperProps;
|
|
176
165
|
}, [
|