@arun-dev/headless 4.6.0 → 4.7.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/chunk-2S4XG4VD.js +33 -0
- package/dist/menu/index.cjs +438 -0
- package/dist/menu/index.d.cts +115 -0
- package/dist/menu/index.d.ts +115 -0
- package/dist/menu/index.js +255 -0
- package/dist/tabs/index.cjs +62 -43
- package/dist/tabs/index.d.cts +15 -4
- package/dist/tabs/index.d.ts +15 -4
- package/dist/tabs/index.js +37 -43
- package/package.json +11 -1
package/dist/tabs/index.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
byDocumentOrder,
|
|
3
|
+
rovingIndex
|
|
4
|
+
} from "../chunk-2S4XG4VD.js";
|
|
1
5
|
import {
|
|
2
6
|
useControlled
|
|
3
7
|
} from "../chunk-UEIB3I3T.js";
|
|
@@ -46,18 +50,13 @@ import { jsx } from "react/jsx-runtime";
|
|
|
46
50
|
function idPart(value) {
|
|
47
51
|
return value.replace(/\s+/g, "_");
|
|
48
52
|
}
|
|
49
|
-
function documentOrder(a, b) {
|
|
50
|
-
const x = a.ref.current;
|
|
51
|
-
const y = b.ref.current;
|
|
52
|
-
if (!x || !y) return 0;
|
|
53
|
-
return x.compareDocumentPosition(y) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;
|
|
54
|
-
}
|
|
55
53
|
function TabsRoot({
|
|
56
54
|
value: valueProp,
|
|
57
55
|
defaultValue,
|
|
58
56
|
onValueChange,
|
|
59
57
|
orientation = "horizontal",
|
|
60
58
|
activationMode = "automatic",
|
|
59
|
+
focusableWhenDisabled: focusableWhenDisabledProp = false,
|
|
61
60
|
className,
|
|
62
61
|
children,
|
|
63
62
|
render,
|
|
@@ -80,12 +79,16 @@ function TabsRoot({
|
|
|
80
79
|
const [tabs, setTabs] = useState([]);
|
|
81
80
|
const register = useCallback((entry) => {
|
|
82
81
|
setTabs(
|
|
83
|
-
(current) => [...current.filter((t) => t.value !== entry.value), entry].sort(
|
|
82
|
+
(current) => [...current.filter((t) => t.value !== entry.value), entry].sort(byDocumentOrder)
|
|
84
83
|
);
|
|
85
84
|
return () => setTabs((current) => current.filter((t) => t !== entry));
|
|
86
85
|
}, []);
|
|
87
|
-
const
|
|
88
|
-
const
|
|
86
|
+
const focusableWhenDisabled = focusableWhenDisabledProp && activationMode === "manual";
|
|
87
|
+
const navigableTabs = useMemo(
|
|
88
|
+
() => focusableWhenDisabled ? tabs : tabs.filter((t) => !t.disabled),
|
|
89
|
+
[tabs, focusableWhenDisabled]
|
|
90
|
+
);
|
|
91
|
+
const tabStopValue = tabs.length === 0 ? value : (navigableTabs.find((t) => t.value === value) ?? navigableTabs[0])?.value;
|
|
89
92
|
const baseId = useId();
|
|
90
93
|
const context = useMemo(
|
|
91
94
|
() => ({
|
|
@@ -93,13 +96,24 @@ function TabsRoot({
|
|
|
93
96
|
setValue,
|
|
94
97
|
orientation,
|
|
95
98
|
activationMode,
|
|
96
|
-
|
|
99
|
+
navigableTabs,
|
|
100
|
+
focusableWhenDisabled,
|
|
97
101
|
tabStopValue,
|
|
98
102
|
register,
|
|
99
103
|
tabId: (v) => `${baseId}-tab-${idPart(v)}`,
|
|
100
104
|
panelId: (v) => `${baseId}-panel-${idPart(v)}`
|
|
101
105
|
}),
|
|
102
|
-
[
|
|
106
|
+
[
|
|
107
|
+
value,
|
|
108
|
+
setValue,
|
|
109
|
+
orientation,
|
|
110
|
+
activationMode,
|
|
111
|
+
navigableTabs,
|
|
112
|
+
focusableWhenDisabled,
|
|
113
|
+
tabStopValue,
|
|
114
|
+
register,
|
|
115
|
+
baseId
|
|
116
|
+
]
|
|
103
117
|
);
|
|
104
118
|
const element = useRender({
|
|
105
119
|
render,
|
|
@@ -112,33 +126,8 @@ function TabsRoot({
|
|
|
112
126
|
|
|
113
127
|
// src/tabs/TabsList.tsx
|
|
114
128
|
import { useRef } from "react";
|
|
115
|
-
|
|
116
|
-
// src/core/rovingFocus.ts
|
|
117
|
-
function rovingIndex(key, {
|
|
118
|
-
count,
|
|
119
|
-
current,
|
|
120
|
-
orientation,
|
|
121
|
-
rtl
|
|
122
|
-
}) {
|
|
123
|
-
if (count === 0) return null;
|
|
124
|
-
const [prevKey, nextKey] = orientation === "vertical" ? ["ArrowUp", "ArrowDown"] : rtl ? ["ArrowRight", "ArrowLeft"] : ["ArrowLeft", "ArrowRight"];
|
|
125
|
-
switch (key) {
|
|
126
|
-
case nextKey:
|
|
127
|
-
return current < 0 ? 0 : (current + 1) % count;
|
|
128
|
-
case prevKey:
|
|
129
|
-
return current < 0 ? count - 1 : (current - 1 + count) % count;
|
|
130
|
-
case "Home":
|
|
131
|
-
return 0;
|
|
132
|
-
case "End":
|
|
133
|
-
return count - 1;
|
|
134
|
-
default:
|
|
135
|
-
return null;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// src/tabs/TabsList.tsx
|
|
140
129
|
function TabsList({ className, children, render, ...rest }) {
|
|
141
|
-
const { orientation,
|
|
130
|
+
const { orientation, navigableTabs } = useTabsRootContext("List");
|
|
142
131
|
const elementRef = useRef(null);
|
|
143
132
|
return useRender({
|
|
144
133
|
render,
|
|
@@ -152,19 +141,19 @@ function TabsList({ className, children, render, ...rest }) {
|
|
|
152
141
|
children,
|
|
153
142
|
ref: elementRef,
|
|
154
143
|
onKeyDown(event) {
|
|
155
|
-
const current =
|
|
144
|
+
const current = navigableTabs.findIndex((t) => t.ref.current === event.target);
|
|
156
145
|
if (current < 0) return;
|
|
157
146
|
const list = elementRef.current;
|
|
158
147
|
const rtl = list ? getComputedStyle(list).direction === "rtl" : false;
|
|
159
148
|
const next = rovingIndex(event.key, {
|
|
160
|
-
count:
|
|
149
|
+
count: navigableTabs.length,
|
|
161
150
|
current,
|
|
162
151
|
orientation,
|
|
163
152
|
rtl
|
|
164
153
|
});
|
|
165
154
|
if (next === null) return;
|
|
166
155
|
event.preventDefault();
|
|
167
|
-
|
|
156
|
+
navigableTabs[next]?.ref.current?.focus();
|
|
168
157
|
}
|
|
169
158
|
},
|
|
170
159
|
consumerProps: rest
|
|
@@ -176,6 +165,7 @@ import { useLayoutEffect, useRef as useRef2 } from "react";
|
|
|
176
165
|
function TabsTab({
|
|
177
166
|
value,
|
|
178
167
|
disabled = false,
|
|
168
|
+
onClick,
|
|
179
169
|
className,
|
|
180
170
|
children,
|
|
181
171
|
render,
|
|
@@ -185,6 +175,7 @@ function TabsTab({
|
|
|
185
175
|
value: selectedValue,
|
|
186
176
|
setValue,
|
|
187
177
|
activationMode,
|
|
178
|
+
focusableWhenDisabled,
|
|
188
179
|
tabStopValue,
|
|
189
180
|
register,
|
|
190
181
|
tabId,
|
|
@@ -206,8 +197,10 @@ function TabsTab({
|
|
|
206
197
|
"aria-selected": selected,
|
|
207
198
|
"aria-controls": panelId(value),
|
|
208
199
|
tabIndex: value === tabStopValue ? 0 : -1,
|
|
209
|
-
// The platform keeps a disabled button out of focus and activation.
|
|
210
|
-
disabled
|
|
200
|
+
// The platform keeps a disabled button out of focus and activation. Focusable when
|
|
201
|
+
// disabled, it is aria-disabled instead, and activation is blocked here.
|
|
202
|
+
disabled: disabled && !focusableWhenDisabled || void 0,
|
|
203
|
+
"aria-disabled": disabled && focusableWhenDisabled || void 0,
|
|
211
204
|
...selectedDataAttributes(selected, disabled),
|
|
212
205
|
className,
|
|
213
206
|
children,
|
|
@@ -219,7 +212,8 @@ function TabsTab({
|
|
|
219
212
|
if (activationMode === "automatic" && !disabled) setValue(value);
|
|
220
213
|
}
|
|
221
214
|
},
|
|
222
|
-
|
|
215
|
+
// A native `disabled` would block your onClick; aria-disabled does not, so it is left off.
|
|
216
|
+
consumerProps: disabled ? rest : { ...rest, onClick }
|
|
223
217
|
});
|
|
224
218
|
}
|
|
225
219
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arun-dev/headless",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.7.0",
|
|
4
4
|
"description": "Unstyled React behaviour primitives — render engine, controlled/uncontrolled state, and data-* state projection. Ships no CSS and no class names.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -64,6 +64,16 @@
|
|
|
64
64
|
"default": "./dist/dialog/index.cjs"
|
|
65
65
|
}
|
|
66
66
|
},
|
|
67
|
+
"./menu": {
|
|
68
|
+
"import": {
|
|
69
|
+
"types": "./dist/menu/index.d.ts",
|
|
70
|
+
"default": "./dist/menu/index.js"
|
|
71
|
+
},
|
|
72
|
+
"require": {
|
|
73
|
+
"types": "./dist/menu/index.d.cts",
|
|
74
|
+
"default": "./dist/menu/index.cjs"
|
|
75
|
+
}
|
|
76
|
+
},
|
|
67
77
|
"./popover": {
|
|
68
78
|
"import": {
|
|
69
79
|
"types": "./dist/popover/index.d.ts",
|