@rainbow-oh/yee-c 0.8.0 → 0.8.1
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/LICENSE +1 -1
- package/README.md +40 -25
- package/dist/cjs/Card/card.js +7 -3
- package/dist/cjs/Carousel/carousel-item.js +7 -2
- package/dist/cjs/Carousel/carousel.js +31 -4
- package/dist/cjs/Cascader/cascader.js +1 -1
- package/dist/cjs/Cascader/column.js +1 -1
- package/dist/cjs/Cascader/node.js +16 -4
- package/dist/cjs/Cascader/search-list.js +13 -4
- package/dist/cjs/Checkbox/checkbox-group.js +1 -1
- package/dist/cjs/Checkbox/checkbox.js +6 -3
- package/dist/cjs/Collapse/panel.js +10 -1
- package/dist/cjs/Dialog/dialog.js +22 -7
- package/dist/cjs/Drawer/drawer.js +14 -5
- package/dist/cjs/Menu/menu-item.js +14 -5
- package/dist/cjs/Menu/menu.js +10 -2
- package/dist/cjs/Pagination/items.js +10 -5
- package/dist/cjs/Pagination/pagination.js +9 -3
- package/dist/cjs/Radio/radio-group.js +6 -5
- package/dist/cjs/Radio/radio.js +39 -29
- package/dist/cjs/Rate/rate.js +6 -1
- package/dist/cjs/Rate/star.js +7 -3
- package/dist/cjs/Steps/step.js +7 -5
- package/dist/cjs/Steps/steps.js +1 -1
- package/dist/cjs/Tabs/tab-content.js +3 -1
- package/dist/cjs/Tabs/tab-header/index.js +3 -1
- package/dist/cjs/Tabs/tab-header/tab-header-title.js +3 -1
- package/dist/cjs/hooks/useDelayState.js +3 -2
- package/dist/cjs/hooks/useFocusManage.d.ts +1 -1
- package/dist/cjs/hooks/useFocusManage.js +27 -22
- package/dist/cjs/style/themes.css +1 -1
- package/dist/cjs/style/variables.css +1 -1
- package/dist/cjs/utils/focusStack.d.ts +6 -2
- package/dist/cjs/utils/focusStack.js +40 -11
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/Card/card.js +7 -3
- package/dist/esm/Carousel/carousel-item.js +7 -2
- package/dist/esm/Carousel/carousel.js +31 -4
- package/dist/esm/Cascader/cascader.js +3 -1
- package/dist/esm/Cascader/column.js +2 -1
- package/dist/esm/Cascader/node.js +18 -4
- package/dist/esm/Cascader/search-list.js +18 -4
- package/dist/esm/Checkbox/checkbox-group.js +3 -1
- package/dist/esm/Checkbox/checkbox.js +6 -3
- package/dist/esm/Collapse/panel.js +11 -2
- package/dist/esm/Dialog/dialog.js +26 -9
- package/dist/esm/Drawer/drawer.js +16 -7
- package/dist/esm/Menu/menu-item.js +16 -5
- package/dist/esm/Menu/menu.js +11 -5
- package/dist/esm/Pagination/items.js +14 -5
- package/dist/esm/Pagination/pagination.js +16 -4
- package/dist/esm/Radio/radio-group.js +5 -4
- package/dist/esm/Radio/radio.js +5 -4
- package/dist/esm/Rate/rate.js +6 -1
- package/dist/esm/Rate/star.js +12 -4
- package/dist/esm/Steps/step.js +12 -6
- package/dist/esm/Steps/steps.js +3 -1
- package/dist/esm/Tabs/tab-content.js +3 -1
- package/dist/esm/Tabs/tab-header/index.js +3 -1
- package/dist/esm/Tabs/tab-header/tab-header-title.js +3 -1
- package/dist/esm/hooks/useDelayState.js +5 -2
- package/dist/esm/hooks/useFocusManage.d.ts +1 -1
- package/dist/esm/hooks/useFocusManage.js +31 -13
- package/dist/esm/style/themes.css +1 -1
- package/dist/esm/style/variables.css +1 -1
- package/dist/esm/utils/focusStack.d.ts +6 -2
- package/dist/esm/utils/focusStack.js +37 -14
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/umd/yee-c.min.js +1 -1
- package/package.json +10 -6
- package/src/Card/card.tsx +5 -1
- package/src/Carousel/carousel-item.tsx +7 -1
- package/src/Carousel/carousel.tsx +30 -3
- package/src/Cascader/cascader.tsx +1 -1
- package/src/Cascader/column.tsx +1 -1
- package/src/Cascader/node.tsx +14 -2
- package/src/Cascader/search-list.tsx +11 -2
- package/src/Checkbox/checkbox-group.tsx +1 -1
- package/src/Checkbox/checkbox.tsx +3 -0
- package/src/Collapse/panel.tsx +11 -1
- package/src/Dialog/dialog.tsx +31 -6
- package/src/Drawer/drawer.tsx +12 -3
- package/src/Menu/menu-item.tsx +12 -3
- package/src/Menu/menu.tsx +11 -5
- package/src/Pagination/items.tsx +9 -4
- package/src/Pagination/pagination.tsx +10 -4
- package/src/Radio/radio-group.tsx +2 -1
- package/src/Radio/radio.tsx +7 -2
- package/src/Rate/rate.tsx +5 -0
- package/src/Rate/star.tsx +8 -4
- package/src/Steps/step.tsx +15 -5
- package/src/Steps/steps.tsx +1 -1
- package/src/Tabs/tab-content.tsx +2 -0
- package/src/Tabs/tab-header/index.tsx +2 -0
- package/src/Tabs/tab-header/tab-header-title.tsx +3 -1
- package/src/hooks/useDelayState.ts +4 -2
- package/src/hooks/useFocusManage.ts +33 -17
- package/src/utils/focusStack.ts +55 -12
- package/src/version.ts +1 -1
|
@@ -179,9 +179,12 @@ const Pagination = React.forwardRef(
|
|
|
179
179
|
prevClick();
|
|
180
180
|
}
|
|
181
181
|
}}
|
|
182
|
+
role="button"
|
|
183
|
+
aria-label="Previous page"
|
|
184
|
+
aria-disabled={mergedCurrent === 1 || undefined}
|
|
182
185
|
key="prev"
|
|
183
186
|
>
|
|
184
|
-
{components?.prev ?? <ChevronLeft size={20} strokeWidth={1.5} />}
|
|
187
|
+
{components?.prev ?? <ChevronLeft size={20} strokeWidth={1.5} aria-hidden="true" />}
|
|
185
188
|
</li>
|
|
186
189
|
);
|
|
187
190
|
};
|
|
@@ -204,9 +207,12 @@ const Pagination = React.forwardRef(
|
|
|
204
207
|
nextClick();
|
|
205
208
|
}
|
|
206
209
|
}}
|
|
210
|
+
role="button"
|
|
211
|
+
aria-label="Next page"
|
|
212
|
+
aria-disabled={mergedCurrent === pageCount || undefined}
|
|
207
213
|
key="next"
|
|
208
214
|
>
|
|
209
|
-
{components?.next ?? <ChevronRight size={20} strokeWidth={1.5} />}
|
|
215
|
+
{components?.next ?? <ChevronRight size={20} strokeWidth={1.5} aria-hidden="true" />}
|
|
210
216
|
</li>
|
|
211
217
|
);
|
|
212
218
|
};
|
|
@@ -249,13 +255,13 @@ const Pagination = React.forwardRef(
|
|
|
249
255
|
}
|
|
250
256
|
|
|
251
257
|
return (
|
|
252
|
-
<ul {...rest} className={cls} style={style} ref={ref}>
|
|
258
|
+
<ul {...rest} className={cls} style={style} ref={ref} role="navigation" aria-label="Pagination" aria-disabled={disabled || undefined}>
|
|
253
259
|
{renderTotal()}
|
|
254
260
|
{renderPrevious()}
|
|
255
261
|
{simple ? (
|
|
256
262
|
<li className={`${prefixCls}-simple`}>
|
|
257
263
|
{renderNumber(false)}
|
|
258
|
-
<span className="split">/</span>
|
|
264
|
+
<span className="split" aria-hidden="true">/</span>
|
|
259
265
|
<span className="total-page">{pageCount}</span>
|
|
260
266
|
</li>
|
|
261
267
|
) : (
|
|
@@ -61,6 +61,8 @@ const RadioGroup = React.forwardRef<HTMLDivElement, RadioGroupProps>(
|
|
|
61
61
|
style={{ ...style, gap: gap }}
|
|
62
62
|
ref={ref}
|
|
63
63
|
role="radiogroup"
|
|
64
|
+
aria-disabled={disabled || undefined}
|
|
65
|
+
{...rest}
|
|
64
66
|
>
|
|
65
67
|
<GroupCtx.Provider
|
|
66
68
|
value={{ name, buttonStyle, onChange: handleChange }}
|
|
@@ -68,7 +70,6 @@ const RadioGroup = React.forwardRef<HTMLDivElement, RadioGroupProps>(
|
|
|
68
70
|
{options?.map((option, index) => (
|
|
69
71
|
<Radio
|
|
70
72
|
disabled={disabled}
|
|
71
|
-
{...rest}
|
|
72
73
|
{...option}
|
|
73
74
|
checked={option.value === mergedValue}
|
|
74
75
|
key={option.value || index}
|
package/src/Radio/radio.tsx
CHANGED
|
@@ -67,7 +67,12 @@ const Radio = React.forwardRef<HTMLInputElement, RadioProps>(
|
|
|
67
67
|
);
|
|
68
68
|
|
|
69
69
|
return (
|
|
70
|
-
<label
|
|
70
|
+
<label
|
|
71
|
+
className={cls}
|
|
72
|
+
style={style}
|
|
73
|
+
onClick={handleClick}
|
|
74
|
+
aria-disabled={disabled || undefined}
|
|
75
|
+
>
|
|
71
76
|
<span
|
|
72
77
|
className={clsx(prefixCls, {
|
|
73
78
|
[`${prefixCls}-disabled`]: disabled,
|
|
@@ -83,12 +88,12 @@ const Radio = React.forwardRef<HTMLInputElement, RadioProps>(
|
|
|
83
88
|
checked={mergedChecked}
|
|
84
89
|
disabled={disabled}
|
|
85
90
|
onChange={handleChange}
|
|
86
|
-
aria-checked={mergedChecked}
|
|
87
91
|
/>
|
|
88
92
|
{!buttonStyle && (
|
|
89
93
|
<span
|
|
90
94
|
className={clsx(`${prefixCls}-inner`, classNames?.inner)}
|
|
91
95
|
style={styles?.inner}
|
|
96
|
+
aria-hidden="true"
|
|
92
97
|
></span>
|
|
93
98
|
)}
|
|
94
99
|
</span>
|
package/src/Rate/rate.tsx
CHANGED
|
@@ -106,6 +106,11 @@ const Rate = React.forwardRef<HTMLUListElement, RateProps>((baseprops, ref) => {
|
|
|
106
106
|
ref={componentRef}
|
|
107
107
|
role="radiogroup"
|
|
108
108
|
aria-label="Rating"
|
|
109
|
+
aria-disabled={disabled || undefined}
|
|
110
|
+
aria-valuemin={0}
|
|
111
|
+
aria-valuemax={count}
|
|
112
|
+
aria-valuenow={mergedValue}
|
|
113
|
+
aria-valuetext={`${mergedValue} out of ${count} stars`}
|
|
109
114
|
>
|
|
110
115
|
{Array(count)
|
|
111
116
|
.fill(0)
|
package/src/Rate/star.tsx
CHANGED
|
@@ -45,6 +45,7 @@ const Star: React.FC<StarProps> = (props) => {
|
|
|
45
45
|
aria-checked={active}
|
|
46
46
|
aria-posinset={index + 1}
|
|
47
47
|
aria-setsize={count}
|
|
48
|
+
aria-label={`${index + 1} ${index + 1 === 1 ? 'star' : 'stars'}`}
|
|
48
49
|
key={'star-' + index}
|
|
49
50
|
>
|
|
50
51
|
<div
|
|
@@ -57,6 +58,7 @@ const Star: React.FC<StarProps> = (props) => {
|
|
|
57
58
|
}
|
|
58
59
|
}}
|
|
59
60
|
tabIndex={0}
|
|
61
|
+
aria-label={`${index + 0.5} stars`}
|
|
60
62
|
key={'star-' + index + 'left'}
|
|
61
63
|
>
|
|
62
64
|
{character ? (
|
|
@@ -64,7 +66,7 @@ const Star: React.FC<StarProps> = (props) => {
|
|
|
64
66
|
{renderCharacter()}
|
|
65
67
|
</span>
|
|
66
68
|
) : (
|
|
67
|
-
<StarHalf className={`${prefixCls}-star-half`} />
|
|
69
|
+
<StarHalf className={`${prefixCls}-star-half`} aria-hidden="true" />
|
|
68
70
|
)}
|
|
69
71
|
</div>
|
|
70
72
|
<div
|
|
@@ -77,6 +79,7 @@ const Star: React.FC<StarProps> = (props) => {
|
|
|
77
79
|
}
|
|
78
80
|
}}
|
|
79
81
|
tabIndex={0}
|
|
82
|
+
aria-label={`${index + 1} ${index + 1 === 1 ? 'star' : 'stars'}`}
|
|
80
83
|
key={'star-' + index + 'right'}
|
|
81
84
|
>
|
|
82
85
|
{character ? (
|
|
@@ -84,7 +87,7 @@ const Star: React.FC<StarProps> = (props) => {
|
|
|
84
87
|
{renderCharacter()}
|
|
85
88
|
</span>
|
|
86
89
|
) : (
|
|
87
|
-
<StarFull className={`${prefixCls}-star-full`} />
|
|
90
|
+
<StarFull className={`${prefixCls}-star-full`} aria-hidden="true" />
|
|
88
91
|
)}
|
|
89
92
|
</div>
|
|
90
93
|
</li>
|
|
@@ -103,6 +106,7 @@ const Star: React.FC<StarProps> = (props) => {
|
|
|
103
106
|
aria-checked={active}
|
|
104
107
|
aria-posinset={index + 1}
|
|
105
108
|
aria-setsize={count}
|
|
109
|
+
aria-label={`${index + 1} ${index + 1 === 1 ? 'star' : 'stars'}`}
|
|
106
110
|
key={'star-' + index}
|
|
107
111
|
>
|
|
108
112
|
{character ? (
|
|
@@ -110,9 +114,9 @@ const Star: React.FC<StarProps> = (props) => {
|
|
|
110
114
|
{renderCharacter()}
|
|
111
115
|
</span>
|
|
112
116
|
) : active ? (
|
|
113
|
-
<StarFull className={`${prefixCls}-star-active`} />
|
|
117
|
+
<StarFull className={`${prefixCls}-star-active`} aria-hidden="true" />
|
|
114
118
|
) : (
|
|
115
|
-
<StarFull className={`${prefixCls}-star-inactive`} />
|
|
119
|
+
<StarFull className={`${prefixCls}-star-inactive`} aria-hidden="true" />
|
|
116
120
|
)}
|
|
117
121
|
</li>
|
|
118
122
|
)}
|
package/src/Steps/step.tsx
CHANGED
|
@@ -43,6 +43,15 @@ const Step: React.FC<StepProps> = (props) => {
|
|
|
43
43
|
status ??
|
|
44
44
|
(index < current ? 'finish' : index === current ? 'process' : 'wait');
|
|
45
45
|
|
|
46
|
+
const statusLabel =
|
|
47
|
+
mergedStatus === 'finish'
|
|
48
|
+
? 'completed'
|
|
49
|
+
: mergedStatus === 'process'
|
|
50
|
+
? 'in progress'
|
|
51
|
+
: mergedStatus === 'error'
|
|
52
|
+
? 'error'
|
|
53
|
+
: 'pending';
|
|
54
|
+
|
|
46
55
|
const renderIcon = useMemo(() => {
|
|
47
56
|
if (dot) return <span className={`${prefixCls}-item-dot`}></span>;
|
|
48
57
|
|
|
@@ -89,7 +98,7 @@ const Step: React.FC<StepProps> = (props) => {
|
|
|
89
98
|
return (
|
|
90
99
|
<>
|
|
91
100
|
{type === 'ribbon' && (
|
|
92
|
-
<div className={`${prefixCls}-item-ribbon-end ${ribbonStatus}`} />
|
|
101
|
+
<div className={`${prefixCls}-item-ribbon-end ${ribbonStatus}`} aria-hidden="true" />
|
|
93
102
|
)}
|
|
94
103
|
<div
|
|
95
104
|
className={cls}
|
|
@@ -97,11 +106,12 @@ const Step: React.FC<StepProps> = (props) => {
|
|
|
97
106
|
onClick={handleClick}
|
|
98
107
|
onKeyDown={handleKeyDown}
|
|
99
108
|
tabIndex={disabled ? -1 : onChange ? 0 : undefined}
|
|
100
|
-
role={onChange ? 'button' :
|
|
109
|
+
role={onChange ? 'button' : 'listitem'}
|
|
101
110
|
aria-disabled={disabled}
|
|
102
111
|
aria-current={index === current ? 'step' : undefined}
|
|
112
|
+
aria-label={`Step ${index + 1}${typeof title === 'string' ? `: ${title}` : ''}, ${statusLabel}`}
|
|
103
113
|
>
|
|
104
|
-
<div className={`${prefixCls}-item-icon`}>{renderIcon}</div>
|
|
114
|
+
<div className={`${prefixCls}-item-icon`} aria-hidden="true">{renderIcon}</div>
|
|
105
115
|
<div className={`${prefixCls}-item-content`}>
|
|
106
116
|
<div className={`${prefixCls}-item-title`}>
|
|
107
117
|
<span title={typeof title === 'string' ? title : undefined}>
|
|
@@ -117,12 +127,12 @@ const Step: React.FC<StepProps> = (props) => {
|
|
|
117
127
|
</div>
|
|
118
128
|
</div>
|
|
119
129
|
{type === 'navigation' && (
|
|
120
|
-
<div className={`${prefixCls}-item-navi-arrow`}>
|
|
130
|
+
<div className={`${prefixCls}-item-navi-arrow`} aria-hidden="true">
|
|
121
131
|
<ChevronRight />
|
|
122
132
|
</div>
|
|
123
133
|
)}
|
|
124
134
|
{type === 'ribbon' && (
|
|
125
|
-
<div className={`${prefixCls}-item-ribbon-head ${ribbonStatus}`} />
|
|
135
|
+
<div className={`${prefixCls}-item-ribbon-head ${ribbonStatus}`} aria-hidden="true" />
|
|
126
136
|
)}
|
|
127
137
|
</>
|
|
128
138
|
);
|
package/src/Steps/steps.tsx
CHANGED
|
@@ -71,7 +71,7 @@ const Steps = forwardRef<HTMLDivElement, StepsProps>((baseprops, ref) => {
|
|
|
71
71
|
};
|
|
72
72
|
|
|
73
73
|
return (
|
|
74
|
-
<div {...rest} className={cls} style={style} ref={ref}>
|
|
74
|
+
<div {...rest} className={cls} style={style} ref={ref} role="list" aria-label="Steps">
|
|
75
75
|
<StepsContext.Provider value={contextValue}>
|
|
76
76
|
{renderSteps()}
|
|
77
77
|
</StepsContext.Provider>
|
package/src/Tabs/tab-content.tsx
CHANGED
|
@@ -67,9 +67,11 @@ const TabContent: React.FC<TabContentProps> = ({
|
|
|
67
67
|
className={`${prefixCls}-tab-content`}
|
|
68
68
|
style={{ display: activeKey === key ? 'block' : 'none' }}
|
|
69
69
|
key={key}
|
|
70
|
+
id={`tabpanel-${key}`}
|
|
70
71
|
role="tabpanel"
|
|
71
72
|
aria-labelledby={`tab-${key}`}
|
|
72
73
|
aria-hidden={activeKey !== key}
|
|
74
|
+
tabIndex={activeKey === key ? 0 : -1}
|
|
73
75
|
>
|
|
74
76
|
{children}
|
|
75
77
|
</div>
|
|
@@ -170,6 +170,8 @@ const TabHeader: React.FC<TabHeaderProps> = (props) => {
|
|
|
170
170
|
className={clsx(`${prefixCls}-nav-list`, classNames?.list)}
|
|
171
171
|
style={styles?.list}
|
|
172
172
|
ref={navList}
|
|
173
|
+
role="tablist"
|
|
174
|
+
aria-orientation={direction}
|
|
173
175
|
>
|
|
174
176
|
{items?.map((item: TabItemType, index: number) => (
|
|
175
177
|
<TabHeaderTitle
|
|
@@ -166,10 +166,12 @@ const TabHeaderTitle: React.FC<TabsItemProps> = (props) => {
|
|
|
166
166
|
{...rest}
|
|
167
167
|
ref={tabRef}
|
|
168
168
|
role="tab"
|
|
169
|
+
id={`tab-${$key}`}
|
|
169
170
|
aria-selected={activeKey === $key}
|
|
170
171
|
aria-disabled={disabled}
|
|
172
|
+
aria-controls={`tabpanel-${$key}`}
|
|
171
173
|
className={cls}
|
|
172
|
-
tabIndex={disabled ? -1 : 0}
|
|
174
|
+
tabIndex={disabled ? -1 : activeKey === $key ? 0 : -1}
|
|
173
175
|
onClick={handleTabClick}
|
|
174
176
|
onKeyDown={handleKeyDown}
|
|
175
177
|
>
|
|
@@ -4,10 +4,12 @@ export default function useDelayState<T>(state: T, delay = 0): T {
|
|
|
4
4
|
const [delayState, setDelayState] = useState<T>();
|
|
5
5
|
|
|
6
6
|
useEffect(() => {
|
|
7
|
-
setTimeout(() => {
|
|
7
|
+
const timer = window.setTimeout(() => {
|
|
8
8
|
setDelayState(state);
|
|
9
9
|
}, delay * 1000);
|
|
10
|
-
|
|
10
|
+
|
|
11
|
+
return () => window.clearTimeout(timer);
|
|
12
|
+
}, [delay, state]);
|
|
11
13
|
|
|
12
14
|
return delayState as T;
|
|
13
15
|
}
|
|
@@ -1,20 +1,36 @@
|
|
|
1
|
-
import { useEffect } from 'react';
|
|
2
|
-
import { popFocus, pushFocus } from '../utils/focusStack';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
import { type FocusStackEntry, popFocus, pushFocus } from '../utils/focusStack';
|
|
3
|
+
export default function useFocusManage(
|
|
4
|
+
element: HTMLElement | null,
|
|
5
|
+
open: boolean,
|
|
6
|
+
) {
|
|
7
|
+
const entryRef = useRef<FocusStackEntry | null>(null);
|
|
8
|
+
const previousFocusRef = useRef<HTMLElement | null>(null);
|
|
9
|
+
const openCycleRef = useRef(false);
|
|
10
|
+
const releaseEntry = () => {
|
|
11
|
+
if (entryRef.current) {
|
|
12
|
+
popFocus(entryRef.current);
|
|
13
|
+
entryRef.current = null;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
const endOpenCycle = () => {
|
|
17
|
+
releaseEntry();
|
|
18
|
+
previousFocusRef.current = null;
|
|
19
|
+
openCycleRef.current = false;
|
|
20
|
+
};
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (open && !openCycleRef.current) {
|
|
23
|
+
openCycleRef.current = true;
|
|
24
|
+
previousFocusRef.current = document.activeElement as HTMLElement | null;
|
|
25
|
+
} else if (!open && openCycleRef.current) {
|
|
26
|
+
endOpenCycle();
|
|
27
|
+
}
|
|
28
|
+
}, [open]);
|
|
8
29
|
useEffect(() => {
|
|
9
|
-
if (!
|
|
10
|
-
|
|
11
|
-
pushFocus(ele);
|
|
12
|
-
} else {
|
|
13
|
-
popFocus();
|
|
30
|
+
if (!open || !openCycleRef.current || !element || entryRef.current) {
|
|
31
|
+
return;
|
|
14
32
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
};
|
|
19
|
-
}, [ele, delayOpen]);
|
|
33
|
+
entryRef.current = pushFocus(element, previousFocusRef.current);
|
|
34
|
+
}, [element, open]);
|
|
35
|
+
useEffect(() => releaseEntry, []);
|
|
20
36
|
}
|
package/src/utils/focusStack.ts
CHANGED
|
@@ -1,22 +1,65 @@
|
|
|
1
|
-
// focusStack.ts
|
|
2
1
|
type Focusable = HTMLElement | null;
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
export interface FocusStackEntry {
|
|
4
|
+
container: HTMLElement;
|
|
5
|
+
previous: Focusable;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const focusableSelector = [
|
|
9
|
+
'a[href]',
|
|
10
|
+
'button:not([disabled])',
|
|
11
|
+
'textarea:not([disabled])',
|
|
12
|
+
'input:not([disabled])',
|
|
13
|
+
'select:not([disabled])',
|
|
14
|
+
'details',
|
|
15
|
+
'[tabindex]:not([tabindex="-1"])',
|
|
16
|
+
].join(',');
|
|
17
|
+
|
|
18
|
+
const stack: FocusStackEntry[] = [];
|
|
5
19
|
let raf = 0;
|
|
6
20
|
|
|
7
|
-
const schedule = (
|
|
21
|
+
const schedule = (callback: () => void) => {
|
|
8
22
|
cancelAnimationFrame(raf);
|
|
9
|
-
raf = requestAnimationFrame(
|
|
23
|
+
raf = requestAnimationFrame(callback);
|
|
10
24
|
};
|
|
11
25
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
26
|
+
const focusInitialElement = (entry: FocusStackEntry) => {
|
|
27
|
+
if (stack[stack.length - 1] !== entry || !entry.container.isConnected) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const activeElement = document.activeElement;
|
|
32
|
+
if (activeElement && entry.container.contains(activeElement)) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const firstFocusable =
|
|
37
|
+
entry.container.querySelector<HTMLElement>(focusableSelector);
|
|
38
|
+
(firstFocusable ?? entry.container).focus();
|
|
16
39
|
};
|
|
17
40
|
|
|
18
|
-
export const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
41
|
+
export const pushFocus = (
|
|
42
|
+
container: HTMLElement,
|
|
43
|
+
previous: Focusable = document.activeElement as HTMLElement | null,
|
|
44
|
+
): FocusStackEntry => {
|
|
45
|
+
const entry = { container, previous };
|
|
46
|
+
stack.push(entry);
|
|
47
|
+
schedule(() => focusInitialElement(entry));
|
|
48
|
+
return entry;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const popFocus = (entry: FocusStackEntry) => {
|
|
52
|
+
const index = stack.lastIndexOf(entry);
|
|
53
|
+
if (index === -1) return;
|
|
54
|
+
|
|
55
|
+
const isTopEntry = index === stack.length - 1;
|
|
56
|
+
stack.splice(index, 1);
|
|
57
|
+
|
|
58
|
+
if (!isTopEntry) return;
|
|
59
|
+
|
|
60
|
+
schedule(() => {
|
|
61
|
+
if (entry.previous?.isConnected) {
|
|
62
|
+
entry.previous.focus();
|
|
63
|
+
}
|
|
64
|
+
});
|
|
22
65
|
};
|