@arun-dev/headless 4.5.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.
@@ -0,0 +1,389 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/tabs/index.ts
21
+ var tabs_exports = {};
22
+ __export(tabs_exports, {
23
+ Tabs: () => index_parts_exports
24
+ });
25
+ module.exports = __toCommonJS(tabs_exports);
26
+
27
+ // src/tabs/index.parts.ts
28
+ var index_parts_exports = {};
29
+ __export(index_parts_exports, {
30
+ List: () => TabsList,
31
+ Panel: () => TabsPanel,
32
+ Root: () => TabsRoot,
33
+ Tab: () => TabsTab
34
+ });
35
+
36
+ // src/tabs/TabsRoot.tsx
37
+ var import_react4 = require("react");
38
+
39
+ // src/core/useControlled.ts
40
+ var import_react = require("react");
41
+ function useControlled({
42
+ controlled,
43
+ default: defaultValue,
44
+ name = "Component",
45
+ state = "value"
46
+ }) {
47
+ const { current: isControlled } = (0, import_react.useRef)(controlled !== void 0);
48
+ const [uncontrolled, setUncontrolled] = (0, import_react.useState)(defaultValue);
49
+ const defaultRef = (0, import_react.useRef)(defaultValue);
50
+ const value = isControlled ? controlled : uncontrolled;
51
+ if (process.env.NODE_ENV !== "production") {
52
+ if (isControlled !== (controlled !== void 0)) {
53
+ console.error(
54
+ `${name}: cannot switch between controlled and uncontrolled \`${state}\`. Decide which one this component is for the whole of its life.`
55
+ );
56
+ }
57
+ if (!isControlled && defaultRef.current !== defaultValue) {
58
+ console.error(
59
+ `${name}: cannot change the default \`${state}\` after mount. It is only read once, so later changes are silently ignored.`
60
+ );
61
+ }
62
+ }
63
+ const setValue = (0, import_react.useCallback)(
64
+ (next) => {
65
+ if (!isControlled) setUncontrolled(next);
66
+ },
67
+ [isControlled]
68
+ );
69
+ return [value, setValue];
70
+ }
71
+
72
+ // src/core/useRender.ts
73
+ var import_react2 = require("react");
74
+
75
+ // src/core/mergeProps.ts
76
+ function isEventHandler(key) {
77
+ return key.length > 2 && key.startsWith("on") && key[2] === key[2]?.toUpperCase();
78
+ }
79
+ function mergeRefs(...refs) {
80
+ return (node) => {
81
+ const cleanups = refs.map((ref) => {
82
+ if (typeof ref === "function") {
83
+ const result = ref(node);
84
+ return typeof result === "function" ? result : () => ref(null);
85
+ }
86
+ if (ref && typeof ref === "object") {
87
+ ref.current = node;
88
+ return () => {
89
+ ref.current = null;
90
+ };
91
+ }
92
+ return void 0;
93
+ });
94
+ return () => {
95
+ cleanups.forEach((cleanup) => {
96
+ if (typeof cleanup === "function") cleanup();
97
+ });
98
+ };
99
+ };
100
+ }
101
+ function isSyntheticEvent(event) {
102
+ return typeof event === "object" && event !== null && "nativeEvent" in event;
103
+ }
104
+ function makeEventPreventable(event) {
105
+ if (Object.prototype.hasOwnProperty.call(event, "preventComponentHandler")) return;
106
+ const target = event;
107
+ target.preventComponentHandler = () => {
108
+ target.componentHandlerPrevented = true;
109
+ };
110
+ }
111
+ function isComponentHandlerPrevented(event) {
112
+ return typeof event === "object" && event !== null && event.componentHandlerPrevented === true;
113
+ }
114
+ function chainHandlers(earlier, value) {
115
+ return (...args) => {
116
+ const event = args[0];
117
+ if (isSyntheticEvent(event)) makeEventPreventable(event);
118
+ value(...args);
119
+ if (isComponentHandlerPrevented(event)) return;
120
+ earlier(...args);
121
+ };
122
+ }
123
+ function mergeProps(...objects) {
124
+ const merged = {};
125
+ for (const props of objects) {
126
+ if (!props) continue;
127
+ for (const key of Object.keys(props)) {
128
+ const value = props[key];
129
+ if (value === void 0) continue;
130
+ if (isEventHandler(key) && typeof value === "function") {
131
+ const earlier = merged[key];
132
+ merged[key] = typeof earlier === "function" ? chainHandlers(earlier, value) : value;
133
+ } else if (key === "className") {
134
+ merged.className = merged.className ? `${String(merged.className)} ${String(value)}` : value;
135
+ } else if (key === "style") {
136
+ merged.style = { ...merged.style, ...value };
137
+ } else if (key === "ref") {
138
+ merged.ref = merged.ref ? mergeRefs(merged.ref, value) : value;
139
+ } else {
140
+ merged[key] = value;
141
+ }
142
+ }
143
+ }
144
+ return merged;
145
+ }
146
+
147
+ // src/core/useRender.ts
148
+ function useRender({
149
+ render,
150
+ defaultTagName,
151
+ props,
152
+ consumerProps
153
+ }) {
154
+ const merged = mergeProps(props, consumerProps);
155
+ if ((0, import_react2.isValidElement)(render)) {
156
+ return (0, import_react2.cloneElement)(render, mergeProps(merged, render.props));
157
+ }
158
+ return (0, import_react2.createElement)(defaultTagName, merged);
159
+ }
160
+
161
+ // src/tabs/TabsRootContext.ts
162
+ var import_react3 = require("react");
163
+ var TabsRootContext = (0, import_react3.createContext)(null);
164
+ function useTabsRootContext(part) {
165
+ const context = (0, import_react3.useContext)(TabsRootContext);
166
+ if (context === null) {
167
+ throw new Error(`<Tabs.${part}> must be rendered inside <Tabs.Root>.`);
168
+ }
169
+ return context;
170
+ }
171
+
172
+ // src/tabs/tabsDataAttributes.ts
173
+ function orientationDataAttributes(orientation) {
174
+ return { "data-orientation": orientation };
175
+ }
176
+ function selectedDataAttributes(selected, disabled = false) {
177
+ return {
178
+ "data-selected": selected ? "" : void 0,
179
+ "data-unselected": selected ? void 0 : "",
180
+ "data-disabled": disabled ? "" : void 0
181
+ };
182
+ }
183
+
184
+ // src/tabs/TabsRoot.tsx
185
+ var import_jsx_runtime = require("react/jsx-runtime");
186
+ function idPart(value) {
187
+ return value.replace(/\s+/g, "_");
188
+ }
189
+ function documentOrder(a, b) {
190
+ const x = a.ref.current;
191
+ const y = b.ref.current;
192
+ if (!x || !y) return 0;
193
+ return x.compareDocumentPosition(y) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;
194
+ }
195
+ function TabsRoot({
196
+ value: valueProp,
197
+ defaultValue,
198
+ onValueChange,
199
+ orientation = "horizontal",
200
+ activationMode = "automatic",
201
+ className,
202
+ children,
203
+ render,
204
+ ...rest
205
+ }) {
206
+ const [value, setValueState] = useControlled({
207
+ controlled: valueProp,
208
+ default: defaultValue,
209
+ name: "Tabs.Root",
210
+ state: "value"
211
+ });
212
+ const setValue = (0, import_react4.useCallback)(
213
+ (next) => {
214
+ if (next === value) return;
215
+ setValueState(next);
216
+ onValueChange?.(next);
217
+ },
218
+ [value, setValueState, onValueChange]
219
+ );
220
+ const [tabs, setTabs] = (0, import_react4.useState)([]);
221
+ const register = (0, import_react4.useCallback)((entry) => {
222
+ setTabs(
223
+ (current) => [...current.filter((t) => t.value !== entry.value), entry].sort(documentOrder)
224
+ );
225
+ return () => setTabs((current) => current.filter((t) => t !== entry));
226
+ }, []);
227
+ const enabledTabs = (0, import_react4.useMemo)(() => tabs.filter((t) => !t.disabled), [tabs]);
228
+ const tabStopValue = tabs.length === 0 ? value : (enabledTabs.find((t) => t.value === value) ?? enabledTabs[0])?.value;
229
+ const baseId = (0, import_react4.useId)();
230
+ const context = (0, import_react4.useMemo)(
231
+ () => ({
232
+ value,
233
+ setValue,
234
+ orientation,
235
+ activationMode,
236
+ enabledTabs,
237
+ tabStopValue,
238
+ register,
239
+ tabId: (v) => `${baseId}-tab-${idPart(v)}`,
240
+ panelId: (v) => `${baseId}-panel-${idPart(v)}`
241
+ }),
242
+ [value, setValue, orientation, activationMode, enabledTabs, tabStopValue, register, baseId]
243
+ );
244
+ const element = useRender({
245
+ render,
246
+ defaultTagName: "div",
247
+ props: { ...orientationDataAttributes(orientation), className, children },
248
+ consumerProps: rest
249
+ });
250
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TabsRootContext.Provider, { value: context, children: element });
251
+ }
252
+
253
+ // src/tabs/TabsList.tsx
254
+ var import_react5 = require("react");
255
+
256
+ // src/core/rovingFocus.ts
257
+ function rovingIndex(key, {
258
+ count,
259
+ current,
260
+ orientation,
261
+ rtl
262
+ }) {
263
+ if (count === 0) return null;
264
+ const [prevKey, nextKey] = orientation === "vertical" ? ["ArrowUp", "ArrowDown"] : rtl ? ["ArrowRight", "ArrowLeft"] : ["ArrowLeft", "ArrowRight"];
265
+ switch (key) {
266
+ case nextKey:
267
+ return current < 0 ? 0 : (current + 1) % count;
268
+ case prevKey:
269
+ return current < 0 ? count - 1 : (current - 1 + count) % count;
270
+ case "Home":
271
+ return 0;
272
+ case "End":
273
+ return count - 1;
274
+ default:
275
+ return null;
276
+ }
277
+ }
278
+
279
+ // src/tabs/TabsList.tsx
280
+ function TabsList({ className, children, render, ...rest }) {
281
+ const { orientation, enabledTabs } = useTabsRootContext("List");
282
+ const elementRef = (0, import_react5.useRef)(null);
283
+ return useRender({
284
+ render,
285
+ defaultTagName: "div",
286
+ props: {
287
+ role: "tablist",
288
+ // Horizontal is the ARIA default for a tablist, so it is only stated when vertical.
289
+ "aria-orientation": orientation === "vertical" ? "vertical" : void 0,
290
+ ...orientationDataAttributes(orientation),
291
+ className,
292
+ children,
293
+ ref: elementRef,
294
+ onKeyDown(event) {
295
+ const current = enabledTabs.findIndex((t) => t.ref.current === event.target);
296
+ if (current < 0) return;
297
+ const list = elementRef.current;
298
+ const rtl = list ? getComputedStyle(list).direction === "rtl" : false;
299
+ const next = rovingIndex(event.key, {
300
+ count: enabledTabs.length,
301
+ current,
302
+ orientation,
303
+ rtl
304
+ });
305
+ if (next === null) return;
306
+ event.preventDefault();
307
+ enabledTabs[next]?.ref.current?.focus();
308
+ }
309
+ },
310
+ consumerProps: rest
311
+ });
312
+ }
313
+
314
+ // src/tabs/TabsTab.tsx
315
+ var import_react6 = require("react");
316
+ function TabsTab({
317
+ value,
318
+ disabled = false,
319
+ className,
320
+ children,
321
+ render,
322
+ ...rest
323
+ }) {
324
+ const {
325
+ value: selectedValue,
326
+ setValue,
327
+ activationMode,
328
+ tabStopValue,
329
+ register,
330
+ tabId,
331
+ panelId
332
+ } = useTabsRootContext("Tab");
333
+ const elementRef = (0, import_react6.useRef)(null);
334
+ const selected = value === selectedValue;
335
+ (0, import_react6.useLayoutEffect)(
336
+ () => register({ value, disabled, ref: elementRef }),
337
+ [register, value, disabled]
338
+ );
339
+ return useRender({
340
+ render,
341
+ defaultTagName: "button",
342
+ props: {
343
+ type: "button",
344
+ role: "tab",
345
+ id: tabId(value),
346
+ "aria-selected": selected,
347
+ "aria-controls": panelId(value),
348
+ tabIndex: value === tabStopValue ? 0 : -1,
349
+ // The platform keeps a disabled button out of focus and activation.
350
+ disabled: disabled || void 0,
351
+ ...selectedDataAttributes(selected, disabled),
352
+ className,
353
+ children,
354
+ ref: elementRef,
355
+ onClick() {
356
+ if (!disabled) setValue(value);
357
+ },
358
+ onFocus() {
359
+ if (activationMode === "automatic" && !disabled) setValue(value);
360
+ }
361
+ },
362
+ consumerProps: rest
363
+ });
364
+ }
365
+
366
+ // src/tabs/TabsPanel.tsx
367
+ function TabsPanel({ value, className, children, render, ...rest }) {
368
+ const { value: selectedValue, tabId, panelId } = useTabsRootContext("Panel");
369
+ const selected = value === selectedValue;
370
+ return useRender({
371
+ render,
372
+ defaultTagName: "div",
373
+ props: {
374
+ role: "tabpanel",
375
+ id: panelId(value),
376
+ "aria-labelledby": tabId(value),
377
+ tabIndex: 0,
378
+ hidden: !selected || void 0,
379
+ ...selectedDataAttributes(selected),
380
+ className,
381
+ children
382
+ },
383
+ consumerProps: rest
384
+ });
385
+ }
386
+ // Annotate the CommonJS export names for ESM import in node:
387
+ 0 && (module.exports = {
388
+ Tabs
389
+ });
@@ -0,0 +1,116 @@
1
+ import * as react from 'react';
2
+ import { ReactElement, Ref, ComponentPropsWithRef } from 'react';
3
+
4
+ /**
5
+ * Roving focus for composite widgets — one Tab stop for the group, arrow keys within it.
6
+ * Tabs first; Menu and Toolbar reuse it. Internal: shared between components, not exported
7
+ * (decision 9).
8
+ *
9
+ * The caller owns the list of items and which are enabled — from component props, never
10
+ * read back from the DOM (decision 10). This only answers "where does this key go".
11
+ */
12
+ type Orientation = 'horizontal' | 'vertical';
13
+
14
+ type TabsActivationMode = 'automatic' | 'manual';
15
+ /** The state Tabs.Root shares with its parts, and projects as `data-*` attributes. */
16
+ type TabsState = {
17
+ value: string | undefined;
18
+ orientation: Orientation;
19
+ };
20
+
21
+ type TabsRootOwnProps = {
22
+ /**
23
+ * Controlled selection: the `value` of the selected Tab. Provide `onValueChange` alongside.
24
+ *
25
+ * Never `undefined` once mounted: the mode is latched at mount, as for Switch.
26
+ */
27
+ value?: string;
28
+ /** Initial selection when uncontrolled. Read once, at mount. Without one, none is selected. */
29
+ defaultValue?: string;
30
+ /** Called with the Tab's `value` whenever the selection changes. */
31
+ onValueChange?: (value: string) => void;
32
+ /** The axis the tabs run along, and so which arrow keys move between them. */
33
+ orientation?: Orientation;
34
+ /**
35
+ * `automatic` selects a tab as soon as it has focus; `manual` waits for Enter, Space or a
36
+ * click. Manual suits panels that are slow to show.
37
+ */
38
+ activationMode?: TabsActivationMode;
39
+ /** Element to render instead of the default `<div>`. Props and ref are merged onto it. */
40
+ render?: ReactElement;
41
+ /** Ref to the rendered element. Merged with any ref on the `render` element. */
42
+ ref?: Ref<HTMLElement>;
43
+ };
44
+ type TabsRootProps = TabsRootOwnProps & Omit<ComponentPropsWithRef<'div'>, keyof TabsRootOwnProps | 'defaultValue' | 'onChange'>;
45
+ /**
46
+ * Tabs, by the WAI-ARIA pattern: a tablist of tabs, one panel each, one selected.
47
+ *
48
+ * There is no native element to lean on (decision 7), so the behaviour is built here: the
49
+ * selection, arrow-key focus with a single Tab stop (roving tabindex, `core/rovingFocus`),
50
+ * and the ids that wire each tab to its panel.
51
+ *
52
+ * Each Tab registers its own `value` and `disabled` from its props; the Root never reads
53
+ * them back from the DOM (decision 10). Elements are kept only to move focus to.
54
+ */
55
+ declare function TabsRoot({ value: valueProp, defaultValue, onValueChange, orientation, activationMode, className, children, render, ...rest }: TabsRootProps): react.JSX.Element;
56
+
57
+ type TabsListOwnProps = {
58
+ /** Element to render instead of the default `<div>`. Props and ref are merged onto it. */
59
+ render?: ReactElement;
60
+ /** Ref to the rendered element. Merged with any ref on the `render` element. */
61
+ ref?: Ref<HTMLElement>;
62
+ };
63
+ type TabsListProps = TabsListOwnProps & Omit<ComponentPropsWithRef<'div'>, keyof TabsListOwnProps>;
64
+ /**
65
+ * The tablist. Holds the keyboard: arrows along the orientation, wrapping at the ends,
66
+ * Home and End — moving focus among the enabled tabs only. Whether focusing a tab also
67
+ * selects it is the Tab's to decide, by `activationMode`.
68
+ *
69
+ * Name it with `aria-label` or `aria-labelledby` when the page has more than one.
70
+ */
71
+ declare function TabsList({ className, children, render, ...rest }: TabsListProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
72
+
73
+ type TabsTabOwnProps = {
74
+ /** Identifies this tab, and the Panel with the same `value`. */
75
+ value: string;
76
+ /** Skipped by the arrow keys and cannot be selected. */
77
+ disabled?: boolean;
78
+ /** Element to render instead of the default `<button>`. Props and ref are merged onto it. */
79
+ render?: ReactElement;
80
+ /** Ref to the rendered element. Merged with any ref on the `render` element. */
81
+ ref?: Ref<HTMLElement>;
82
+ };
83
+ type TabsTabProps = TabsTabOwnProps & Omit<ComponentPropsWithRef<'button'>, keyof TabsTabOwnProps | 'value'>;
84
+ /**
85
+ * One tab. A native `<button>`, so Enter, Space and a click select it with no key handling
86
+ * here. Only the tab holding the group's Tab stop has `tabIndex={0}` — roving tabindex — so
87
+ * Tab enters the list at the selected tab and leaves it after one press.
88
+ *
89
+ * With `activationMode="automatic"` (the default) focus selects: the arrow keys change the
90
+ * panel as they move. With `"manual"` they only move focus.
91
+ */
92
+ declare function TabsTab({ value, disabled, className, children, render, ...rest }: TabsTabProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
93
+
94
+ type TabsPanelOwnProps = {
95
+ /** The `value` of the Tab this panel belongs to. */
96
+ value: string;
97
+ /** Element to render instead of the default `<div>`. Props and ref are merged onto it. */
98
+ render?: ReactElement;
99
+ /** Ref to the rendered element. Merged with any ref on the `render` element. */
100
+ ref?: Ref<HTMLElement>;
101
+ };
102
+ type TabsPanelProps = TabsPanelOwnProps & Omit<ComponentPropsWithRef<'div'>, keyof TabsPanelOwnProps>;
103
+ /**
104
+ * The content for one tab, labelled by it. Hidden with the `hidden` attribute while its tab
105
+ * is not selected, and stays mounted, so what is inside keeps its state between visits.
106
+ *
107
+ * `tabIndex={0}`, as the pattern recommends, so a panel with nothing focusable inside is
108
+ * still reached by Tab after the tablist.
109
+ */
110
+ declare function TabsPanel({ value, className, children, render, ...rest }: TabsPanelProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
111
+
112
+ declare namespace index_parts {
113
+ export { TabsList as List, TabsPanel as Panel, TabsRoot as Root, TabsTab as Tab };
114
+ }
115
+
116
+ export { index_parts as Tabs, type TabsActivationMode, type TabsListProps, type TabsPanelProps, type TabsRootProps, type TabsState, type TabsTabProps };
@@ -0,0 +1,116 @@
1
+ import * as react from 'react';
2
+ import { ReactElement, Ref, ComponentPropsWithRef } from 'react';
3
+
4
+ /**
5
+ * Roving focus for composite widgets — one Tab stop for the group, arrow keys within it.
6
+ * Tabs first; Menu and Toolbar reuse it. Internal: shared between components, not exported
7
+ * (decision 9).
8
+ *
9
+ * The caller owns the list of items and which are enabled — from component props, never
10
+ * read back from the DOM (decision 10). This only answers "where does this key go".
11
+ */
12
+ type Orientation = 'horizontal' | 'vertical';
13
+
14
+ type TabsActivationMode = 'automatic' | 'manual';
15
+ /** The state Tabs.Root shares with its parts, and projects as `data-*` attributes. */
16
+ type TabsState = {
17
+ value: string | undefined;
18
+ orientation: Orientation;
19
+ };
20
+
21
+ type TabsRootOwnProps = {
22
+ /**
23
+ * Controlled selection: the `value` of the selected Tab. Provide `onValueChange` alongside.
24
+ *
25
+ * Never `undefined` once mounted: the mode is latched at mount, as for Switch.
26
+ */
27
+ value?: string;
28
+ /** Initial selection when uncontrolled. Read once, at mount. Without one, none is selected. */
29
+ defaultValue?: string;
30
+ /** Called with the Tab's `value` whenever the selection changes. */
31
+ onValueChange?: (value: string) => void;
32
+ /** The axis the tabs run along, and so which arrow keys move between them. */
33
+ orientation?: Orientation;
34
+ /**
35
+ * `automatic` selects a tab as soon as it has focus; `manual` waits for Enter, Space or a
36
+ * click. Manual suits panels that are slow to show.
37
+ */
38
+ activationMode?: TabsActivationMode;
39
+ /** Element to render instead of the default `<div>`. Props and ref are merged onto it. */
40
+ render?: ReactElement;
41
+ /** Ref to the rendered element. Merged with any ref on the `render` element. */
42
+ ref?: Ref<HTMLElement>;
43
+ };
44
+ type TabsRootProps = TabsRootOwnProps & Omit<ComponentPropsWithRef<'div'>, keyof TabsRootOwnProps | 'defaultValue' | 'onChange'>;
45
+ /**
46
+ * Tabs, by the WAI-ARIA pattern: a tablist of tabs, one panel each, one selected.
47
+ *
48
+ * There is no native element to lean on (decision 7), so the behaviour is built here: the
49
+ * selection, arrow-key focus with a single Tab stop (roving tabindex, `core/rovingFocus`),
50
+ * and the ids that wire each tab to its panel.
51
+ *
52
+ * Each Tab registers its own `value` and `disabled` from its props; the Root never reads
53
+ * them back from the DOM (decision 10). Elements are kept only to move focus to.
54
+ */
55
+ declare function TabsRoot({ value: valueProp, defaultValue, onValueChange, orientation, activationMode, className, children, render, ...rest }: TabsRootProps): react.JSX.Element;
56
+
57
+ type TabsListOwnProps = {
58
+ /** Element to render instead of the default `<div>`. Props and ref are merged onto it. */
59
+ render?: ReactElement;
60
+ /** Ref to the rendered element. Merged with any ref on the `render` element. */
61
+ ref?: Ref<HTMLElement>;
62
+ };
63
+ type TabsListProps = TabsListOwnProps & Omit<ComponentPropsWithRef<'div'>, keyof TabsListOwnProps>;
64
+ /**
65
+ * The tablist. Holds the keyboard: arrows along the orientation, wrapping at the ends,
66
+ * Home and End — moving focus among the enabled tabs only. Whether focusing a tab also
67
+ * selects it is the Tab's to decide, by `activationMode`.
68
+ *
69
+ * Name it with `aria-label` or `aria-labelledby` when the page has more than one.
70
+ */
71
+ declare function TabsList({ className, children, render, ...rest }: TabsListProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
72
+
73
+ type TabsTabOwnProps = {
74
+ /** Identifies this tab, and the Panel with the same `value`. */
75
+ value: string;
76
+ /** Skipped by the arrow keys and cannot be selected. */
77
+ disabled?: boolean;
78
+ /** Element to render instead of the default `<button>`. Props and ref are merged onto it. */
79
+ render?: ReactElement;
80
+ /** Ref to the rendered element. Merged with any ref on the `render` element. */
81
+ ref?: Ref<HTMLElement>;
82
+ };
83
+ type TabsTabProps = TabsTabOwnProps & Omit<ComponentPropsWithRef<'button'>, keyof TabsTabOwnProps | 'value'>;
84
+ /**
85
+ * One tab. A native `<button>`, so Enter, Space and a click select it with no key handling
86
+ * here. Only the tab holding the group's Tab stop has `tabIndex={0}` — roving tabindex — so
87
+ * Tab enters the list at the selected tab and leaves it after one press.
88
+ *
89
+ * With `activationMode="automatic"` (the default) focus selects: the arrow keys change the
90
+ * panel as they move. With `"manual"` they only move focus.
91
+ */
92
+ declare function TabsTab({ value, disabled, className, children, render, ...rest }: TabsTabProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
93
+
94
+ type TabsPanelOwnProps = {
95
+ /** The `value` of the Tab this panel belongs to. */
96
+ value: string;
97
+ /** Element to render instead of the default `<div>`. Props and ref are merged onto it. */
98
+ render?: ReactElement;
99
+ /** Ref to the rendered element. Merged with any ref on the `render` element. */
100
+ ref?: Ref<HTMLElement>;
101
+ };
102
+ type TabsPanelProps = TabsPanelOwnProps & Omit<ComponentPropsWithRef<'div'>, keyof TabsPanelOwnProps>;
103
+ /**
104
+ * The content for one tab, labelled by it. Hidden with the `hidden` attribute while its tab
105
+ * is not selected, and stays mounted, so what is inside keeps its state between visits.
106
+ *
107
+ * `tabIndex={0}`, as the pattern recommends, so a panel with nothing focusable inside is
108
+ * still reached by Tab after the tablist.
109
+ */
110
+ declare function TabsPanel({ value, className, children, render, ...rest }: TabsPanelProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
111
+
112
+ declare namespace index_parts {
113
+ export { TabsList as List, TabsPanel as Panel, TabsRoot as Root, TabsTab as Tab };
114
+ }
115
+
116
+ export { index_parts as Tabs, type TabsActivationMode, type TabsListProps, type TabsPanelProps, type TabsRootProps, type TabsState, type TabsTabProps };
@@ -0,0 +1,248 @@
1
+ import {
2
+ useControlled
3
+ } from "../chunk-UEIB3I3T.js";
4
+ import {
5
+ __export,
6
+ useRender
7
+ } from "../chunk-OQTWWZ4B.js";
8
+
9
+ // src/tabs/index.parts.ts
10
+ var index_parts_exports = {};
11
+ __export(index_parts_exports, {
12
+ List: () => TabsList,
13
+ Panel: () => TabsPanel,
14
+ Root: () => TabsRoot,
15
+ Tab: () => TabsTab
16
+ });
17
+
18
+ // src/tabs/TabsRoot.tsx
19
+ import { useCallback, useId, useMemo, useState } from "react";
20
+
21
+ // src/tabs/TabsRootContext.ts
22
+ import { createContext, useContext } from "react";
23
+ var TabsRootContext = createContext(null);
24
+ function useTabsRootContext(part) {
25
+ const context = useContext(TabsRootContext);
26
+ if (context === null) {
27
+ throw new Error(`<Tabs.${part}> must be rendered inside <Tabs.Root>.`);
28
+ }
29
+ return context;
30
+ }
31
+
32
+ // src/tabs/tabsDataAttributes.ts
33
+ function orientationDataAttributes(orientation) {
34
+ return { "data-orientation": orientation };
35
+ }
36
+ function selectedDataAttributes(selected, disabled = false) {
37
+ return {
38
+ "data-selected": selected ? "" : void 0,
39
+ "data-unselected": selected ? void 0 : "",
40
+ "data-disabled": disabled ? "" : void 0
41
+ };
42
+ }
43
+
44
+ // src/tabs/TabsRoot.tsx
45
+ import { jsx } from "react/jsx-runtime";
46
+ function idPart(value) {
47
+ return value.replace(/\s+/g, "_");
48
+ }
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
+ function TabsRoot({
56
+ value: valueProp,
57
+ defaultValue,
58
+ onValueChange,
59
+ orientation = "horizontal",
60
+ activationMode = "automatic",
61
+ className,
62
+ children,
63
+ render,
64
+ ...rest
65
+ }) {
66
+ const [value, setValueState] = useControlled({
67
+ controlled: valueProp,
68
+ default: defaultValue,
69
+ name: "Tabs.Root",
70
+ state: "value"
71
+ });
72
+ const setValue = useCallback(
73
+ (next) => {
74
+ if (next === value) return;
75
+ setValueState(next);
76
+ onValueChange?.(next);
77
+ },
78
+ [value, setValueState, onValueChange]
79
+ );
80
+ const [tabs, setTabs] = useState([]);
81
+ const register = useCallback((entry) => {
82
+ setTabs(
83
+ (current) => [...current.filter((t) => t.value !== entry.value), entry].sort(documentOrder)
84
+ );
85
+ return () => setTabs((current) => current.filter((t) => t !== entry));
86
+ }, []);
87
+ const enabledTabs = useMemo(() => tabs.filter((t) => !t.disabled), [tabs]);
88
+ const tabStopValue = tabs.length === 0 ? value : (enabledTabs.find((t) => t.value === value) ?? enabledTabs[0])?.value;
89
+ const baseId = useId();
90
+ const context = useMemo(
91
+ () => ({
92
+ value,
93
+ setValue,
94
+ orientation,
95
+ activationMode,
96
+ enabledTabs,
97
+ tabStopValue,
98
+ register,
99
+ tabId: (v) => `${baseId}-tab-${idPart(v)}`,
100
+ panelId: (v) => `${baseId}-panel-${idPart(v)}`
101
+ }),
102
+ [value, setValue, orientation, activationMode, enabledTabs, tabStopValue, register, baseId]
103
+ );
104
+ const element = useRender({
105
+ render,
106
+ defaultTagName: "div",
107
+ props: { ...orientationDataAttributes(orientation), className, children },
108
+ consumerProps: rest
109
+ });
110
+ return /* @__PURE__ */ jsx(TabsRootContext.Provider, { value: context, children: element });
111
+ }
112
+
113
+ // src/tabs/TabsList.tsx
114
+ 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
+ function TabsList({ className, children, render, ...rest }) {
141
+ const { orientation, enabledTabs } = useTabsRootContext("List");
142
+ const elementRef = useRef(null);
143
+ return useRender({
144
+ render,
145
+ defaultTagName: "div",
146
+ props: {
147
+ role: "tablist",
148
+ // Horizontal is the ARIA default for a tablist, so it is only stated when vertical.
149
+ "aria-orientation": orientation === "vertical" ? "vertical" : void 0,
150
+ ...orientationDataAttributes(orientation),
151
+ className,
152
+ children,
153
+ ref: elementRef,
154
+ onKeyDown(event) {
155
+ const current = enabledTabs.findIndex((t) => t.ref.current === event.target);
156
+ if (current < 0) return;
157
+ const list = elementRef.current;
158
+ const rtl = list ? getComputedStyle(list).direction === "rtl" : false;
159
+ const next = rovingIndex(event.key, {
160
+ count: enabledTabs.length,
161
+ current,
162
+ orientation,
163
+ rtl
164
+ });
165
+ if (next === null) return;
166
+ event.preventDefault();
167
+ enabledTabs[next]?.ref.current?.focus();
168
+ }
169
+ },
170
+ consumerProps: rest
171
+ });
172
+ }
173
+
174
+ // src/tabs/TabsTab.tsx
175
+ import { useLayoutEffect, useRef as useRef2 } from "react";
176
+ function TabsTab({
177
+ value,
178
+ disabled = false,
179
+ className,
180
+ children,
181
+ render,
182
+ ...rest
183
+ }) {
184
+ const {
185
+ value: selectedValue,
186
+ setValue,
187
+ activationMode,
188
+ tabStopValue,
189
+ register,
190
+ tabId,
191
+ panelId
192
+ } = useTabsRootContext("Tab");
193
+ const elementRef = useRef2(null);
194
+ const selected = value === selectedValue;
195
+ useLayoutEffect(
196
+ () => register({ value, disabled, ref: elementRef }),
197
+ [register, value, disabled]
198
+ );
199
+ return useRender({
200
+ render,
201
+ defaultTagName: "button",
202
+ props: {
203
+ type: "button",
204
+ role: "tab",
205
+ id: tabId(value),
206
+ "aria-selected": selected,
207
+ "aria-controls": panelId(value),
208
+ tabIndex: value === tabStopValue ? 0 : -1,
209
+ // The platform keeps a disabled button out of focus and activation.
210
+ disabled: disabled || void 0,
211
+ ...selectedDataAttributes(selected, disabled),
212
+ className,
213
+ children,
214
+ ref: elementRef,
215
+ onClick() {
216
+ if (!disabled) setValue(value);
217
+ },
218
+ onFocus() {
219
+ if (activationMode === "automatic" && !disabled) setValue(value);
220
+ }
221
+ },
222
+ consumerProps: rest
223
+ });
224
+ }
225
+
226
+ // src/tabs/TabsPanel.tsx
227
+ function TabsPanel({ value, className, children, render, ...rest }) {
228
+ const { value: selectedValue, tabId, panelId } = useTabsRootContext("Panel");
229
+ const selected = value === selectedValue;
230
+ return useRender({
231
+ render,
232
+ defaultTagName: "div",
233
+ props: {
234
+ role: "tabpanel",
235
+ id: panelId(value),
236
+ "aria-labelledby": tabId(value),
237
+ tabIndex: 0,
238
+ hidden: !selected || void 0,
239
+ ...selectedDataAttributes(selected),
240
+ className,
241
+ children
242
+ },
243
+ consumerProps: rest
244
+ });
245
+ }
246
+ export {
247
+ index_parts_exports as Tabs
248
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arun-dev/headless",
3
- "version": "4.5.1",
3
+ "version": "4.6.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",
@@ -94,6 +94,16 @@
94
94
  "default": "./dist/switch/index.cjs"
95
95
  }
96
96
  },
97
+ "./tabs": {
98
+ "import": {
99
+ "types": "./dist/tabs/index.d.ts",
100
+ "default": "./dist/tabs/index.js"
101
+ },
102
+ "require": {
103
+ "types": "./dist/tabs/index.d.cts",
104
+ "default": "./dist/tabs/index.cjs"
105
+ }
106
+ },
97
107
  "./tooltip": {
98
108
  "import": {
99
109
  "types": "./dist/tooltip/index.d.ts",