@moneyforward/mfui-components 4.10.2 → 4.10.3
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/README.md
CHANGED
|
@@ -9,7 +9,6 @@ React components for basic UI parts and layouts.
|
|
|
9
9
|
## Library Requirements
|
|
10
10
|
|
|
11
11
|
MFUI requires certain libraries. Please check their versions.
|
|
12
|
-
You can also check the recommended environment by referring to the libraries used in the projects under the `examples/` directory.
|
|
13
12
|
|
|
14
13
|
### Requirements
|
|
15
14
|
|
|
@@ -65,14 +65,18 @@ export function useKeyboardNavigation({ isOptionPanelOpen, loading, enableSearch
|
|
|
65
65
|
if (activeElement?.getAttribute('role') === 'option' && optionPanelRef.current?.contains(activeElement)) {
|
|
66
66
|
return;
|
|
67
67
|
}
|
|
68
|
-
// When
|
|
69
|
-
|
|
68
|
+
// When search is enabled, focus the search input regardless of the selected option or loading state.
|
|
69
|
+
// Moving the focus away while the user is typing would commit or discard the IME composition.
|
|
70
|
+
if (enableSearchOptions && searchInputRef.current) {
|
|
71
|
+
searchInputRef.current.focus({ preventScroll: true });
|
|
72
|
+
}
|
|
73
|
+
else if (loading) {
|
|
74
|
+
// When loading without a search input, there is no option to focus yet, so focus the option panel
|
|
75
|
+
// root div to enable ESC key functionality
|
|
70
76
|
optionPanelRef.current?.focus({ preventScroll: true });
|
|
71
|
-
return;
|
|
72
77
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
searchInputRef.current?.focus({ preventScroll: true });
|
|
78
|
+
if (loading) {
|
|
79
|
+
return;
|
|
76
80
|
}
|
|
77
81
|
// Handle auto-scroll independently of search input focus
|
|
78
82
|
// This allows both features to work together
|
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
import { useCallback } from 'react';
|
|
2
|
+
import { getDeepActiveElement } from './getDeepActiveElement';
|
|
2
3
|
import { getFocusableNodes } from './getFocusableNodes';
|
|
4
|
+
/**
|
|
5
|
+
* Returns the focused node as seen by the focus trap.
|
|
6
|
+
*
|
|
7
|
+
* `document.activeElement` stops at the shadow host when the wrapper is rendered inside a shadow root,
|
|
8
|
+
* so the lookup starts from the wrapper's own root node (this also works for closed shadow roots) and
|
|
9
|
+
* follows nested shadow roots down to the deepest focused element.
|
|
10
|
+
* That element is then mapped back onto `focusableNodes` by ascending shadow boundaries only, so focus
|
|
11
|
+
* inside a web component that is itself a focusable node of the trap still resolves to that host.
|
|
12
|
+
*
|
|
13
|
+
* @param wrapper - The focus trap container.
|
|
14
|
+
*
|
|
15
|
+
* @param focusableNodes - The focusable nodes inside the wrapper.
|
|
16
|
+
*
|
|
17
|
+
* @returns The focusable node holding focus, or the deepest focused element when none of them does.
|
|
18
|
+
*/
|
|
19
|
+
const getFocusingNode = (wrapper, focusableNodes) => {
|
|
20
|
+
const rootNode = wrapper.getRootNode();
|
|
21
|
+
const deepActiveElement = getDeepActiveElement(rootNode instanceof ShadowRoot ? rootNode : wrapper.ownerDocument);
|
|
22
|
+
let current = deepActiveElement;
|
|
23
|
+
while (current) {
|
|
24
|
+
if (focusableNodes.includes(current))
|
|
25
|
+
return current;
|
|
26
|
+
const currentRootNode = current.getRootNode();
|
|
27
|
+
// Only a ShadowRoot has a host to continue from; a Document ends the walk.
|
|
28
|
+
current = currentRootNode instanceof ShadowRoot ? currentRootNode.host : null;
|
|
29
|
+
}
|
|
30
|
+
return deepActiveElement;
|
|
31
|
+
};
|
|
3
32
|
/**
|
|
4
33
|
* Hook to trap focus within a container.
|
|
5
34
|
*
|
|
@@ -16,8 +45,8 @@ export function useFocusTrap({ onKeyDown, onPressingTabOnNotFocusableElement } =
|
|
|
16
45
|
if (event.key !== 'Tab')
|
|
17
46
|
return;
|
|
18
47
|
const wrapper = event.currentTarget;
|
|
19
|
-
const focusingElement = wrapper.ownerDocument.activeElement;
|
|
20
48
|
const focusableNodes = getFocusableNodes(wrapper);
|
|
49
|
+
const focusingElement = getFocusingNode(wrapper, focusableNodes);
|
|
21
50
|
const [firstFocusableNode] = focusableNodes;
|
|
22
51
|
const lastFocusableNode = focusableNodes.at(-1);
|
|
23
52
|
const focusingOnFirstNode = focusingElement === firstFocusableNode;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moneyforward/mfui-components",
|
|
3
|
-
"version": "4.10.
|
|
3
|
+
"version": "4.10.3",
|
|
4
4
|
"description": "React UI Component Library for all Money Forward products",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -31,11 +31,11 @@
|
|
|
31
31
|
],
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@pandacss/dev": "1.12.1",
|
|
34
|
-
"@storybook/addon-a11y": "10.6.
|
|
35
|
-
"@storybook/addon-docs": "10.6.
|
|
36
|
-
"@storybook/addon-links": "10.6.
|
|
34
|
+
"@storybook/addon-a11y": "10.6.5",
|
|
35
|
+
"@storybook/addon-docs": "10.6.5",
|
|
36
|
+
"@storybook/addon-links": "10.6.5",
|
|
37
37
|
"@storybook/addon-mcp": "0.7.0",
|
|
38
|
-
"@storybook/react-vite": "10.6.
|
|
38
|
+
"@storybook/react-vite": "10.6.5",
|
|
39
39
|
"@testing-library/jest-dom": "6.9.1",
|
|
40
40
|
"@testing-library/react": "16.3.3",
|
|
41
41
|
"@testing-library/user-event": "14.6.8",
|
|
@@ -43,15 +43,15 @@
|
|
|
43
43
|
"@types/react": "19.2.9",
|
|
44
44
|
"@types/react-dom": "19.2.3",
|
|
45
45
|
"@vitejs/plugin-react": "5.2.0",
|
|
46
|
-
"@vitest/coverage-v8": "4.1.
|
|
46
|
+
"@vitest/coverage-v8": "4.1.12",
|
|
47
47
|
"jsdom": "26.1.0",
|
|
48
48
|
"oxlint-config-mfui": "^1.0.0",
|
|
49
49
|
"react": "19.2.3",
|
|
50
50
|
"react-dom": "19.2.3",
|
|
51
|
-
"storybook": "10.6.
|
|
51
|
+
"storybook": "10.6.5",
|
|
52
52
|
"ts-node": "10.9.2",
|
|
53
53
|
"tsx": "4.23.15",
|
|
54
|
-
"vitest": "4.1.
|
|
54
|
+
"vitest": "4.1.12"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
57
|
"react": ">=18",
|