-
Notifications
You must be signed in to change notification settings - Fork 0
/
n-item.js
149 lines (135 loc) · 4.83 KB
/
n-item.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import React, { cloneElement, useContext, useEffect, useId, useState } from "react";
import { ContextForItem } from "./context";
export default function Item({ children, type, orderI, contentItemStyle, transRunning }) {
const isTrigger = type === 'T';
const isContent = type === 'C';
const context = useContext(ContextForItem);
const ariaId = useId();
const { triggerAriaIds, contentAriaIds, prevMenuIdxRef, openedMenuIdx, isKeyActive, setActivePanel } = context;
const [controlContentId, setControl] = useState(); // 收起 slate 会移除 dom,因此动态设置 id
useEffect(() => {
if (openedMenuIdx > -1 && prevMenuIdxRef.current < 0) {
setControl(contentAriaIds.current[orderI]);
} else if (openedMenuIdx < 0) {
setControl(null);
}
}, [openedMenuIdx, orderI, controlContentId]);
if (isTrigger) {
const { btnsRef, overMenu, leaveMenu } = context;
const openedMenu = openedMenuIdx === orderI;
triggerAriaIds.current[orderI] = ariaId;
/** 点击菜单按钮 */
const clickMenuBtn = e => {
const target = e.target;
let targetIdx = btnsRef.current.findIndex(e => e === target);
if (targetIdx < 0) targetIdx = btnsRef.current.findIndex(e => e.contains(target));
if (targetIdx > -1) {
isKeyActive.current = e.nativeEvent.offsetX === 0 && e.nativeEvent.offsetY === 0;
if (targetIdx === openedMenuIdx) {
// 关闭菜单
setActivePanel(-1);
} else {
// 打开菜单
setActivePanel(targetIdx);
}
}
};
const triggerProps = {
ref: e => btnsRef.current[orderI] = e,
onClick: clickMenuBtn,
onMouseOver: overMenu,
onMouseLeave: leaveMenu,
id: ariaId,
"aria-expanded": openedMenu,
"aria-controls": controlContentId,
};
// render props
if (typeof children === "function")
return children(triggerProps);
// component
return cloneElement(children, triggerProps);
}
if (isContent) {
const {
panelsRef,
headFocusItemInContent,
tailFocusItemInContent,
checkedFocusOwnerContent,
prevMenuIdxRef,
onlyKeyFocus,
contentWrapperRef
} = context;
const openedMenu = openedMenuIdx === orderI;
contentAriaIds.current[orderI] = ariaId;
/** 菜单面板上的键盘操作 */
const onKeyDown = e => {
if (e.key === "Escape" || e.key === "Esc" || e.keyCode === 27) {
// 关闭菜单
isKeyActive.current = true;
setActivePanel(-1);
return;
}
if (e.key === "Tab" || e.keyCode === 9) {
// 动画进行时期,禁止 tab,避免聚焦引起的样式错位
if (transRunning?.current) {
e.preventDefault();
return;
}
// 非键盘模式下切换菜单之后,按下 tab
if (!checkedFocusOwnerContent.current && prevMenuIdxRef.current > -1 && onlyKeyFocus && !isKeyActive.current) {
const activeE = document.activeElement;
if (contentWrapperRef.current?.contains(activeE)) { // 焦点在所有面板的 wrapper 中
const focusTarget = panelsRef.current[openedMenuIdx]; // 当前面板
if (!focusTarget.contains(activeE)) { // 焦点不在当前面板
checkedFocusOwnerContent.current = true;
focusTarget.focus({ preventScroll: true });
e.preventDefault();
return;
}
}
}
}
const head = headFocusItemInContent.current[orderI]
const tail = tailFocusItemInContent.current[orderI];
// 焦点矫正
if (e.target === panelsRef.current[orderI]) {
if (e.key === "Tab" || e.keyCode === 9) {
if (e.shiftKey) {
tail && tail.focus();
} else {
head && head.focus();
}
e.preventDefault();
}
}
// 回尾
if (e.target === head && (e.key === "Tab" || e.keyCode === 9) && e.shiftKey) {
tail && tail.focus();
e.preventDefault();
}
// 回头
if (e.target === tail && (e.key === "Tab" || e.keyCode === 9) && !e.shiftKey) {
head && head.focus();
e.preventDefault();
}
};
const contentProps = {
onKeyDown,
ref: e => panelsRef.current[orderI] = e,
style: contentItemStyle,
id: ariaId,
"aria-labelledby": triggerAriaIds.current[orderI],
"aria-hidden": !openedMenu,
tabIndex: -1
};
const getHead = e => headFocusItemInContent.current[orderI] = e;
const getTail = e => tailFocusItemInContent.current[orderI] = e;
// render props
if (typeof children === "function")
return children(contentProps, getHead, getTail);
// component
return cloneElement(children, { head: getHead, tail: getTail, propsFromN: contentProps });
}
return children;
}
Item.displayName = "Item";