@funstack/ui 0.0.0-stage → 0.1.0-alpha.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/LICENSE +21 -0
- package/README.md +42 -2
- package/dist/accordion/accordion-context.d.mts +5 -0
- package/dist/accordion/accordion-context.d.mts.map +1 -0
- package/dist/accordion/accordion-context.mjs +9 -0
- package/dist/accordion/accordion-context.mjs.map +1 -0
- package/dist/accordion/accordion-item.d.mts +53 -0
- package/dist/accordion/accordion-item.d.mts.map +1 -0
- package/dist/accordion/accordion-item.mjs +85 -0
- package/dist/accordion/accordion-item.mjs.map +1 -0
- package/dist/accordion/accordion-panel.d.mts +40 -0
- package/dist/accordion/accordion-panel.d.mts.map +1 -0
- package/dist/accordion/accordion-panel.mjs +40 -0
- package/dist/accordion/accordion-panel.mjs.map +1 -0
- package/dist/accordion/accordion-root.d.mts +90 -0
- package/dist/accordion/accordion-root.d.mts.map +1 -0
- package/dist/accordion/accordion-root.mjs +229 -0
- package/dist/accordion/accordion-root.mjs.map +1 -0
- package/dist/accordion/accordion-trigger.d.mts +37 -0
- package/dist/accordion/accordion-trigger.d.mts.map +1 -0
- package/dist/accordion/accordion-trigger.mjs +81 -0
- package/dist/accordion/accordion-trigger.mjs.map +1 -0
- package/dist/accordion/index.d.mts +20 -0
- package/dist/accordion/index.d.mts.map +1 -0
- package/dist/accordion/index.mjs +20 -0
- package/dist/accordion/index.mjs.map +1 -0
- package/dist/checkbox/checkbox.d.mts +92 -0
- package/dist/checkbox/checkbox.d.mts.map +1 -0
- package/dist/checkbox/checkbox.mjs +82 -0
- package/dist/checkbox/checkbox.mjs.map +1 -0
- package/dist/checkbox/index.d.mts +3 -0
- package/dist/checkbox/index.mjs +1 -0
- package/dist/dialog/dialog-close.d.mts +44 -0
- package/dist/dialog/dialog-close.d.mts.map +1 -0
- package/dist/dialog/dialog-close.mjs +76 -0
- package/dist/dialog/dialog-close.mjs.map +1 -0
- package/dist/dialog/dialog-context.mjs +13 -0
- package/dist/dialog/dialog-context.mjs.map +1 -0
- package/dist/dialog/dialog-description.d.mts +22 -0
- package/dist/dialog/dialog-description.d.mts.map +1 -0
- package/dist/dialog/dialog-description.mjs +31 -0
- package/dist/dialog/dialog-description.mjs.map +1 -0
- package/dist/dialog/dialog-form.d.mts +58 -0
- package/dist/dialog/dialog-form.d.mts.map +1 -0
- package/dist/dialog/dialog-form.mjs +108 -0
- package/dist/dialog/dialog-form.mjs.map +1 -0
- package/dist/dialog/dialog-popup.d.mts +81 -0
- package/dist/dialog/dialog-popup.d.mts.map +1 -0
- package/dist/dialog/dialog-popup.mjs +366 -0
- package/dist/dialog/dialog-popup.mjs.map +1 -0
- package/dist/dialog/dialog-root.d.mts +70 -0
- package/dist/dialog/dialog-root.d.mts.map +1 -0
- package/dist/dialog/dialog-root.mjs +105 -0
- package/dist/dialog/dialog-root.mjs.map +1 -0
- package/dist/dialog/dialog-title.d.mts +23 -0
- package/dist/dialog/dialog-title.d.mts.map +1 -0
- package/dist/dialog/dialog-title.mjs +31 -0
- package/dist/dialog/dialog-title.mjs.map +1 -0
- package/dist/dialog/dialog-trigger.d.mts +39 -0
- package/dist/dialog/dialog-trigger.d.mts.map +1 -0
- package/dist/dialog/dialog-trigger.mjs +80 -0
- package/dist/dialog/dialog-trigger.mjs.map +1 -0
- package/dist/dialog/index.d.mts +28 -0
- package/dist/dialog/index.d.mts.map +1 -0
- package/dist/dialog/index.mjs +28 -0
- package/dist/dialog/index.mjs.map +1 -0
- package/dist/index.d.mts +38 -0
- package/dist/index.mjs +9 -0
- package/dist/internal/action-details.mjs +47 -0
- package/dist/internal/action-details.mjs.map +1 -0
- package/dist/internal/data-attr.mjs +13 -0
- package/dist/internal/data-attr.mjs.map +1 -0
- package/dist/internal/merge-props.mjs +119 -0
- package/dist/internal/merge-props.mjs.map +1 -0
- package/dist/internal/platform-attributes.d.mts +49 -0
- package/dist/internal/platform-attributes.d.mts.map +1 -0
- package/dist/internal/transition-boundary.mjs +52 -0
- package/dist/internal/transition-boundary.mjs.map +1 -0
- package/dist/internal/transition-types.mjs +15 -0
- package/dist/internal/transition-types.mjs.map +1 -0
- package/dist/internal/types.d.mts +38 -0
- package/dist/internal/types.d.mts.map +1 -0
- package/dist/internal/use-controllable-action.mjs +115 -0
- package/dist/internal/use-controllable-action.mjs.map +1 -0
- package/dist/internal/use-dev-warning.mjs +21 -0
- package/dist/internal/use-dev-warning.mjs.map +1 -0
- package/dist/internal/use-form-reset.mjs +50 -0
- package/dist/internal/use-form-reset.mjs.map +1 -0
- package/dist/internal/use-native-checked.mjs +113 -0
- package/dist/internal/use-native-checked.mjs.map +1 -0
- package/dist/internal/use-native-group-reconciliation.mjs +64 -0
- package/dist/internal/use-native-group-reconciliation.mjs.map +1 -0
- package/dist/internal/use-render.mjs +51 -0
- package/dist/internal/use-render.mjs.map +1 -0
- package/dist/internal/use-until-found.mjs +40 -0
- package/dist/internal/use-until-found.mjs.map +1 -0
- package/dist/radio-group/index.d.mts +20 -0
- package/dist/radio-group/index.d.mts.map +1 -0
- package/dist/radio-group/index.mjs +20 -0
- package/dist/radio-group/index.mjs.map +1 -0
- package/dist/radio-group/radio-group-context.mjs +13 -0
- package/dist/radio-group/radio-group-context.mjs.map +1 -0
- package/dist/radio-group/radio-group-item.d.mts +54 -0
- package/dist/radio-group/radio-group-item.d.mts.map +1 -0
- package/dist/radio-group/radio-group-item.mjs +88 -0
- package/dist/radio-group/radio-group-item.mjs.map +1 -0
- package/dist/radio-group/radio-group-label.d.mts +21 -0
- package/dist/radio-group/radio-group-label.d.mts.map +1 -0
- package/dist/radio-group/radio-group-label.mjs +30 -0
- package/dist/radio-group/radio-group-label.mjs.map +1 -0
- package/dist/radio-group/radio-group-root.d.mts +92 -0
- package/dist/radio-group/radio-group-root.d.mts.map +1 -0
- package/dist/radio-group/radio-group-root.mjs +137 -0
- package/dist/radio-group/radio-group-root.mjs.map +1 -0
- package/dist/switch/index.d.mts +3 -0
- package/dist/switch/index.mjs +1 -0
- package/dist/switch/switch.d.mts +78 -0
- package/dist/switch/switch.d.mts.map +1 -0
- package/dist/switch/switch.mjs +77 -0
- package/dist/switch/switch.mjs.map +1 -0
- package/dist/tabs/index.d.mts +24 -0
- package/dist/tabs/index.d.mts.map +1 -0
- package/dist/tabs/index.mjs +23 -0
- package/dist/tabs/index.mjs.map +1 -0
- package/dist/tabs/tabs-context.d.mts +7 -0
- package/dist/tabs/tabs-context.d.mts.map +1 -0
- package/dist/tabs/tabs-context.mjs +13 -0
- package/dist/tabs/tabs-context.mjs.map +1 -0
- package/dist/tabs/tabs-ids.mjs +39 -0
- package/dist/tabs/tabs-ids.mjs.map +1 -0
- package/dist/tabs/tabs-indicator.d.mts +32 -0
- package/dist/tabs/tabs-indicator.d.mts.map +1 -0
- package/dist/tabs/tabs-indicator.mjs +49 -0
- package/dist/tabs/tabs-indicator.mjs.map +1 -0
- package/dist/tabs/tabs-list.d.mts +34 -0
- package/dist/tabs/tabs-list.d.mts.map +1 -0
- package/dist/tabs/tabs-list.mjs +122 -0
- package/dist/tabs/tabs-list.mjs.map +1 -0
- package/dist/tabs/tabs-panel.d.mts +50 -0
- package/dist/tabs/tabs-panel.d.mts.map +1 -0
- package/dist/tabs/tabs-panel.mjs +81 -0
- package/dist/tabs/tabs-panel.mjs.map +1 -0
- package/dist/tabs/tabs-root.d.mts +64 -0
- package/dist/tabs/tabs-root.d.mts.map +1 -0
- package/dist/tabs/tabs-root.mjs +90 -0
- package/dist/tabs/tabs-root.mjs.map +1 -0
- package/dist/tabs/tabs-tab.d.mts +49 -0
- package/dist/tabs/tabs-tab.d.mts.map +1 -0
- package/dist/tabs/tabs-tab.mjs +86 -0
- package/dist/tabs/tabs-tab.mjs.map +1 -0
- package/dist/toggle/index.d.mts +3 -0
- package/dist/toggle/index.mjs +1 -0
- package/dist/toggle/toggle.d.mts +57 -0
- package/dist/toggle/toggle.d.mts.map +1 -0
- package/dist/toggle/toggle.mjs +59 -0
- package/dist/toggle/toggle.mjs.map +1 -0
- package/dist/tooltip/index.d.mts +20 -0
- package/dist/tooltip/index.d.mts.map +1 -0
- package/dist/tooltip/index.mjs +19 -0
- package/dist/tooltip/index.mjs.map +1 -0
- package/dist/tooltip/tooltip-context.d.mts +5 -0
- package/dist/tooltip/tooltip-context.d.mts.map +1 -0
- package/dist/tooltip/tooltip-context.mjs +13 -0
- package/dist/tooltip/tooltip-context.mjs.map +1 -0
- package/dist/tooltip/tooltip-popup.d.mts +49 -0
- package/dist/tooltip/tooltip-popup.d.mts.map +1 -0
- package/dist/tooltip/tooltip-popup.mjs +57 -0
- package/dist/tooltip/tooltip-popup.mjs.map +1 -0
- package/dist/tooltip/tooltip-root.d.mts +41 -0
- package/dist/tooltip/tooltip-root.d.mts.map +1 -0
- package/dist/tooltip/tooltip-root.mjs +86 -0
- package/dist/tooltip/tooltip-root.mjs.map +1 -0
- package/dist/tooltip/tooltip-trigger.d.mts +38 -0
- package/dist/tooltip/tooltip-trigger.d.mts.map +1 -0
- package/dist/tooltip/tooltip-trigger.mjs +55 -0
- package/dist/tooltip/tooltip-trigger.mjs.map +1 -0
- package/dist/tooltip/use-tooltip-interaction.mjs +131 -0
- package/dist/tooltip/use-tooltip-interaction.mjs.map +1 -0
- package/package.json +70 -3
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { ActionDetails, ChangeAction, ChangeReason, RenderProp, StateValue } from "../internal/types.mjs";
|
|
2
|
+
import { CSSProperties, ComponentPropsWithRef, ViewTransitionClass } from "react";
|
|
3
|
+
//#region src/switch/switch.d.ts
|
|
4
|
+
export interface SwitchState {
|
|
5
|
+
/** Optimistic checked state: what the user sees, including an in-flight change. */
|
|
6
|
+
readonly checked: boolean;
|
|
7
|
+
/** An action dispatched by this Switch is in flight (including the resulting render). */
|
|
8
|
+
readonly pending: boolean;
|
|
9
|
+
/** The `disabled` prop. */
|
|
10
|
+
readonly disabled: boolean;
|
|
11
|
+
}
|
|
12
|
+
/** Details passed as the second argument of `checkedChangeAction`. */
|
|
13
|
+
export interface SwitchCheckedChangeDetails extends ActionDetails {
|
|
14
|
+
/**
|
|
15
|
+
* `"activate"`: the user toggled the switch (click, Space, label click); `event` is the native
|
|
16
|
+
* `click` event. `"platform"`: the browser changed the value itself: a form reset (`event` is
|
|
17
|
+
* the `reset` event) or a click before hydration / Firefox form-state restoration found on
|
|
18
|
+
* mount (`event` is `undefined`).
|
|
19
|
+
*/
|
|
20
|
+
readonly reason: ChangeReason;
|
|
21
|
+
}
|
|
22
|
+
export interface SwitchProps extends Omit<ComponentPropsWithRef<"input">, "type" | "role" | "checked" | "defaultChecked" | "onChange" | "aria-checked" | "className" | "style" | "children"> {
|
|
23
|
+
/** Controlled checked state. `undefined` means uncontrolled. */
|
|
24
|
+
checked?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Initial checked state in uncontrolled mode (default `false`). Also the value a form reset
|
|
27
|
+
* returns to: the latest prop value is mirrored into the input's `defaultChecked`.
|
|
28
|
+
*/
|
|
29
|
+
defaultChecked?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Called with the next checked state on every change. Runs inside the Switch's transition; may
|
|
32
|
+
* be sync or async (also a Server Function). Rejections reach the nearest Error Boundary.
|
|
33
|
+
* Also called with `details.reason === "platform"` for a form reset or a pre-hydration click.
|
|
34
|
+
*
|
|
35
|
+
* A `setState` placed after your own `await` is not a Transition update; wrap it in
|
|
36
|
+
* `startTransition` (React limitation). `details.event.preventDefault()` is not a veto and is
|
|
37
|
+
* unsupported here; use controlled mode to veto a change.
|
|
38
|
+
*/
|
|
39
|
+
checkedChangeAction?: ChangeAction<boolean, SwitchCheckedChangeDetails>;
|
|
40
|
+
/** Renders a natively disabled input; never dispatches. Default `false`. */
|
|
41
|
+
disabled?: boolean;
|
|
42
|
+
/** Submitted as the input's value when checked. Default `"on"` (platform). */
|
|
43
|
+
value?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Forwarded as `default` to the Switch's `<ViewTransition>`. Default: no boundary. Animates the
|
|
46
|
+
* commit that ends an action, not the optimistic flip; animate the flip with CSS on `:checked`.
|
|
47
|
+
*
|
|
48
|
+
* Static: without `transition` the part renders no `<ViewTransition>`; changing it between
|
|
49
|
+
* `undefined` and a value remounts the element. Any value, `"none"` included, makes a mount or
|
|
50
|
+
* `<Activity>` reveal of this part inside a transition start a document view transition: the
|
|
51
|
+
* page cross-fades for about 250 ms and the next transition commit waits for it (issue #35).
|
|
52
|
+
*/
|
|
53
|
+
transition?: ViewTransitionClass;
|
|
54
|
+
className?: StateValue<string | undefined, SwitchState>;
|
|
55
|
+
style?: StateValue<CSSProperties | undefined, SwitchState>;
|
|
56
|
+
/**
|
|
57
|
+
* Replace the rendered element (element form) or take over rendering (function form). Must keep
|
|
58
|
+
* an `<input type="checkbox">`.
|
|
59
|
+
*/
|
|
60
|
+
render?: RenderProp<ComponentPropsWithRef<"input">, SwitchState>;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* An on/off switch (WAI-ARIA "Switch" pattern, HTML checkbox input variant). Renders
|
|
64
|
+
* `<input type="checkbox" role="switch">` whose DOM `checked` is the optimistic state; the input
|
|
65
|
+
* carries `data-pending` while `checkedChangeAction` is in flight and takes part in forms natively.
|
|
66
|
+
*
|
|
67
|
+
* Owned keys (a `render` element's value is overridden): `type`, `role`, `checked`,
|
|
68
|
+
* `defaultChecked`, `aria-checked` (always removed), `disabled`, `data-checked`, `data-pending`,
|
|
69
|
+
* `data-disabled`. Event handlers are merged rather than owned: a `render` element's own
|
|
70
|
+
* `onChange` (or `onClick`) runs first, then the library's; an `onClick` that calls
|
|
71
|
+
* `preventDefault()` cancels the change (nothing is dispatched).
|
|
72
|
+
*/
|
|
73
|
+
export declare function Switch({ checked, defaultChecked, checkedChangeAction, disabled, transition, className, style, render, ...rest }: SwitchProps): import("react").ReactNode;
|
|
74
|
+
export declare namespace Switch {
|
|
75
|
+
var displayName: string;
|
|
76
|
+
}
|
|
77
|
+
//#endregion
|
|
78
|
+
//# sourceMappingURL=switch.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"switch.d.mts","names":[],"sources":["../../src/switch/switch.tsx"],"mappings":";;;iBAiBiB;;WAEN;;WAEA;;WAEA;;;iBAIM,mCAAmC;;;;;;;WAOzC,QAAQ;;iBAGF,oBAAoB,KACnC;;EAYA;;;;;EAKA;;;;;;;;;;EAUA,sBAAsB,sBAAsB;;EAE5C;;EAEA;;;;;;;;;;EAUA,aAAa;EACb,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;;;;;EAK9C,SAAS,WAAW,gCAAgC;;;;;;;;;;;;;wBActC,SACd,SACA,gBACA,qBACA,UACA,YACA,WACA,OACA,WACG,QACF,8BAAW;yBAVE"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useTransitionBoundary } from "../internal/transition-boundary.mjs";
|
|
3
|
+
import { createActionDetails } from "../internal/action-details.mjs";
|
|
4
|
+
import { dataAttr } from "../internal/data-attr.mjs";
|
|
5
|
+
import { transitionTypes } from "../internal/transition-types.mjs";
|
|
6
|
+
import { useControllableAction } from "../internal/use-controllable-action.mjs";
|
|
7
|
+
import { useRender } from "../internal/use-render.mjs";
|
|
8
|
+
import { guardCheckableChange, useNativeChecked } from "../internal/use-native-checked.mjs";
|
|
9
|
+
//#region src/switch/switch.tsx
|
|
10
|
+
/**
|
|
11
|
+
* An on/off switch (WAI-ARIA "Switch" pattern, HTML checkbox input variant). Renders
|
|
12
|
+
* `<input type="checkbox" role="switch">` whose DOM `checked` is the optimistic state; the input
|
|
13
|
+
* carries `data-pending` while `checkedChangeAction` is in flight and takes part in forms natively.
|
|
14
|
+
*
|
|
15
|
+
* Owned keys (a `render` element's value is overridden): `type`, `role`, `checked`,
|
|
16
|
+
* `defaultChecked`, `aria-checked` (always removed), `disabled`, `data-checked`, `data-pending`,
|
|
17
|
+
* `data-disabled`. Event handlers are merged rather than owned: a `render` element's own
|
|
18
|
+
* `onChange` (or `onClick`) runs first, then the library's; an `onClick` that calls
|
|
19
|
+
* `preventDefault()` cancels the change (nothing is dispatched).
|
|
20
|
+
*/
|
|
21
|
+
function Switch({ checked, defaultChecked, checkedChangeAction, disabled = false, transition, className, style, render, ...rest }) {
|
|
22
|
+
const { optimistic, pending, dispatch, latestInFlight } = useControllableAction({
|
|
23
|
+
controlled: checked,
|
|
24
|
+
defaultValue: defaultChecked,
|
|
25
|
+
initialValue: false,
|
|
26
|
+
action: checkedChangeAction,
|
|
27
|
+
transitionTypes: (next) => transitionTypes("switch", next ? "on" : "off"),
|
|
28
|
+
warn: {
|
|
29
|
+
component: "Switch",
|
|
30
|
+
prop: "checked"
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
const inputRef = useNativeChecked({
|
|
34
|
+
type: "checkbox",
|
|
35
|
+
component: "Switch",
|
|
36
|
+
checked: optimistic,
|
|
37
|
+
defaultChecked: checked === void 0 ? defaultChecked ?? false : void 0,
|
|
38
|
+
latestInFlight,
|
|
39
|
+
onPlatformChange: (next, event) => dispatch(next, createActionDetails(event, { reason: "platform" }))
|
|
40
|
+
});
|
|
41
|
+
const element = useRender({
|
|
42
|
+
defaultElement: "input",
|
|
43
|
+
render,
|
|
44
|
+
state: {
|
|
45
|
+
checked: optimistic,
|
|
46
|
+
pending,
|
|
47
|
+
disabled
|
|
48
|
+
},
|
|
49
|
+
className,
|
|
50
|
+
style,
|
|
51
|
+
children: void 0,
|
|
52
|
+
ref: inputRef,
|
|
53
|
+
props: rest,
|
|
54
|
+
libraryProps: {
|
|
55
|
+
type: "checkbox",
|
|
56
|
+
role: "switch",
|
|
57
|
+
checked: optimistic,
|
|
58
|
+
defaultChecked: void 0,
|
|
59
|
+
"aria-checked": void 0,
|
|
60
|
+
disabled,
|
|
61
|
+
"data-checked": dataAttr(optimistic),
|
|
62
|
+
"data-pending": dataAttr(pending),
|
|
63
|
+
"data-disabled": dataAttr(disabled),
|
|
64
|
+
onChange: (event) => {
|
|
65
|
+
if (disabled) return;
|
|
66
|
+
const nativeEvent = event.nativeEvent;
|
|
67
|
+
guardCheckableChange(nativeEvent, () => dispatch(!optimistic, createActionDetails(nativeEvent, { reason: "activate" })), "Switch");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
return useTransitionBoundary("Switch", transition, element);
|
|
72
|
+
}
|
|
73
|
+
Switch.displayName = "Switch";
|
|
74
|
+
//#endregion
|
|
75
|
+
export { Switch };
|
|
76
|
+
|
|
77
|
+
//# sourceMappingURL=switch.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"switch.mjs","names":[],"sources":["../../src/switch/switch.tsx"],"sourcesContent":["\"use client\";\nimport type { ChangeEvent, ComponentPropsWithRef, CSSProperties, ViewTransitionClass } from \"react\";\nimport { useTransitionBoundary } from \"../internal/transition-boundary.js\";\nimport { createActionDetails } from \"../internal/action-details.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport { transitionTypes } from \"../internal/transition-types.js\";\nimport type {\n ActionDetails,\n ChangeAction,\n ChangeReason,\n RenderProp,\n StateValue,\n} from \"../internal/types.js\";\nimport { useControllableAction } from \"../internal/use-controllable-action.js\";\nimport { guardCheckableChange, useNativeChecked } from \"../internal/use-native-checked.js\";\nimport { useRender } from \"../internal/use-render.js\";\n\nexport interface SwitchState {\n /** Optimistic checked state: what the user sees, including an in-flight change. */\n readonly checked: boolean;\n /** An action dispatched by this Switch is in flight (including the resulting render). */\n readonly pending: boolean;\n /** The `disabled` prop. */\n readonly disabled: boolean;\n}\n\n/** Details passed as the second argument of `checkedChangeAction`. */\nexport interface SwitchCheckedChangeDetails extends ActionDetails {\n /**\n * `\"activate\"`: the user toggled the switch (click, Space, label click); `event` is the native\n * `click` event. `\"platform\"`: the browser changed the value itself: a form reset (`event` is\n * the `reset` event) or a click before hydration / Firefox form-state restoration found on\n * mount (`event` is `undefined`).\n */\n readonly reason: ChangeReason;\n}\n\nexport interface SwitchProps extends Omit<\n ComponentPropsWithRef<\"input\">,\n | \"type\"\n | \"role\"\n | \"checked\"\n | \"defaultChecked\"\n | \"onChange\"\n | \"aria-checked\"\n | \"className\"\n | \"style\"\n | \"children\"\n> {\n /** Controlled checked state. `undefined` means uncontrolled. */\n checked?: boolean;\n /**\n * Initial checked state in uncontrolled mode (default `false`). Also the value a form reset\n * returns to: the latest prop value is mirrored into the input's `defaultChecked`.\n */\n defaultChecked?: boolean;\n /**\n * Called with the next checked state on every change. Runs inside the Switch's transition; may\n * be sync or async (also a Server Function). Rejections reach the nearest Error Boundary.\n * Also called with `details.reason === \"platform\"` for a form reset or a pre-hydration click.\n *\n * A `setState` placed after your own `await` is not a Transition update; wrap it in\n * `startTransition` (React limitation). `details.event.preventDefault()` is not a veto and is\n * unsupported here; use controlled mode to veto a change.\n */\n checkedChangeAction?: ChangeAction<boolean, SwitchCheckedChangeDetails>;\n /** Renders a natively disabled input; never dispatches. Default `false`. */\n disabled?: boolean;\n /** Submitted as the input's value when checked. Default `\"on\"` (platform). */\n value?: string;\n /**\n * Forwarded as `default` to the Switch's `<ViewTransition>`. Default: no boundary. Animates the\n * commit that ends an action, not the optimistic flip; animate the flip with CSS on `:checked`.\n *\n * Static: without `transition` the part renders no `<ViewTransition>`; changing it between\n * `undefined` and a value remounts the element. Any value, `\"none\"` included, makes a mount or\n * `<Activity>` reveal of this part inside a transition start a document view transition: the\n * page cross-fades for about 250 ms and the next transition commit waits for it (issue #35).\n */\n transition?: ViewTransitionClass;\n className?: StateValue<string | undefined, SwitchState>;\n style?: StateValue<CSSProperties | undefined, SwitchState>;\n /**\n * Replace the rendered element (element form) or take over rendering (function form). Must keep\n * an `<input type=\"checkbox\">`.\n */\n render?: RenderProp<ComponentPropsWithRef<\"input\">, SwitchState>;\n}\n\n/**\n * An on/off switch (WAI-ARIA \"Switch\" pattern, HTML checkbox input variant). Renders\n * `<input type=\"checkbox\" role=\"switch\">` whose DOM `checked` is the optimistic state; the input\n * carries `data-pending` while `checkedChangeAction` is in flight and takes part in forms natively.\n *\n * Owned keys (a `render` element's value is overridden): `type`, `role`, `checked`,\n * `defaultChecked`, `aria-checked` (always removed), `disabled`, `data-checked`, `data-pending`,\n * `data-disabled`. Event handlers are merged rather than owned: a `render` element's own\n * `onChange` (or `onClick`) runs first, then the library's; an `onClick` that calls\n * `preventDefault()` cancels the change (nothing is dispatched).\n */\nexport function Switch({\n checked,\n defaultChecked,\n checkedChangeAction,\n disabled = false,\n transition,\n className,\n style,\n render,\n ...rest\n}: SwitchProps) {\n const { optimistic, pending, dispatch, latestInFlight } = useControllableAction<\n boolean,\n SwitchCheckedChangeDetails\n >({\n controlled: checked,\n defaultValue: defaultChecked,\n initialValue: false,\n action: checkedChangeAction,\n transitionTypes: (next) => transitionTypes(\"switch\", next ? \"on\" : \"off\"),\n warn: { component: \"Switch\", prop: \"checked\" },\n });\n\n const inputRef = useNativeChecked({\n type: \"checkbox\",\n component: \"Switch\",\n checked: optimistic,\n // Uncontrolled: the latest prop is the reset target. Controlled: React's mount-time value.\n defaultChecked: checked === undefined ? (defaultChecked ?? false) : undefined,\n latestInFlight,\n onPlatformChange: (next, event) =>\n dispatch(next, createActionDetails(event, { reason: \"platform\" })),\n });\n\n const state: SwitchState = { checked: optimistic, pending, disabled };\n\n const element = useRender<ComponentPropsWithRef<\"input\">, SwitchState>({\n defaultElement: \"input\",\n render,\n state,\n className,\n style,\n children: undefined,\n ref: inputRef,\n // Pass-through layer: a `render` element's own props win over these.\n props: rest,\n // Library-controlled layer (owned keys, conventions 1.2).\n libraryProps: {\n type: \"checkbox\",\n role: \"switch\",\n // The DOM state is the optimistic state (conventions 3.2): react-dom restores a controlled\n // input to its props after every event, and only the sync-lane optimistic value survives.\n checked: optimistic,\n defaultChecked: undefined,\n \"aria-checked\": undefined,\n // Owned so that native disabledness, `data-disabled` and the click guard always agree: a\n // `render` element cannot re-enable (`disabled={false}`) or disable the input on its own.\n disabled,\n \"data-checked\": dataAttr(optimistic),\n \"data-pending\": dataAttr(pending),\n \"data-disabled\": dataAttr(disabled),\n onChange: (event: ChangeEvent<HTMLInputElement>) => {\n // A natively disabled input fires no click; this covers `render` elements that do not\n // honour `disabled`.\n if (disabled) return;\n const nativeEvent = event.nativeEvent;\n guardCheckableChange(\n nativeEvent,\n () => dispatch(!optimistic, createActionDetails(nativeEvent, { reason: \"activate\" })),\n \"Switch\",\n );\n },\n },\n });\n\n return useTransitionBoundary(\"Switch\", transition, element);\n}\n\nSwitch.displayName = \"Switch\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAoGA,SAAgB,OAAO,EACrB,SACA,gBACA,qBACA,WAAW,OACX,YACA,WACA,OACA,QACA,GAAG,QACW;CACd,MAAM,EAAE,YAAY,SAAS,UAAU,mBAAmB,sBAGxD;EACA,YAAY;EACZ,cAAc;EACd,cAAc;EACd,QAAQ;EACR,kBAAkB,SAAS,gBAAgB,UAAU,OAAO,OAAO,KAAK;EACxE,MAAM;GAAE,WAAW;GAAU,MAAM;EAAU;CAC/C,CAAC;CAED,MAAM,WAAW,iBAAiB;EAChC,MAAM;EACN,WAAW;EACX,SAAS;EAET,gBAAgB,YAAY,KAAA,IAAa,kBAAkB,QAAS,KAAA;EACpE;EACA,mBAAmB,MAAM,UACvB,SAAS,MAAM,oBAAoB,OAAO,EAAE,QAAQ,WAAW,CAAC,CAAC;CACrE,CAAC;CAID,MAAM,UAAU,UAAuD;EACrE,gBAAgB;EAChB;EACA,OAAA;GAL2B,SAAS;GAAY;GAAS;EAKrD;EACJ;EACA;EACA,UAAU,KAAA;EACV,KAAK;EAEL,OAAO;EAEP,cAAc;GACZ,MAAM;GACN,MAAM;GAGN,SAAS;GACT,gBAAgB,KAAA;GAChB,gBAAgB,KAAA;GAGhB;GACA,gBAAgB,SAAS,UAAU;GACnC,gBAAgB,SAAS,OAAO;GAChC,iBAAiB,SAAS,QAAQ;GAClC,WAAW,UAAyC;IAGlD,IAAI,UAAU;IACd,MAAM,cAAc,MAAM;IAC1B,qBACE,mBACM,SAAS,CAAC,YAAY,oBAAoB,aAAa,EAAE,QAAQ,WAAW,CAAC,CAAC,GACpF,QACF;GACF;EACF;CACF,CAAC;CAED,OAAO,sBAAsB,UAAU,YAAY,OAAO;AAC5D;AAEA,OAAO,cAAc"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import "../internal/platform-attributes.mjs";
|
|
2
|
+
import { TabsActivationMode, TabsOrientation } from "./tabs-context.mjs";
|
|
3
|
+
import { TabsIndicator, TabsIndicatorProps, TabsIndicatorState } from "./tabs-indicator.mjs";
|
|
4
|
+
import { TabsList, TabsListProps, TabsListState } from "./tabs-list.mjs";
|
|
5
|
+
import { TabsPanel, TabsPanelProps, TabsPanelState } from "./tabs-panel.mjs";
|
|
6
|
+
import { TabsRoot, TabsRootProps, TabsRootState, TabsValueChangeDetails } from "./tabs-root.mjs";
|
|
7
|
+
import { TabsTab, TabsTabProps, TabsTabState } from "./tabs-tab.mjs";
|
|
8
|
+
//#region src/tabs/index.d.ts
|
|
9
|
+
/**
|
|
10
|
+
* Tabs (WAI-ARIA APG Tabs pattern): `Root` (div) > `List` (tablist) > `Tab` (button) and an
|
|
11
|
+
* optional `Indicator` (span), plus one `Panel` (tabpanel) per tab. Tabs and the Indicator render
|
|
12
|
+
* the optimistic selection; Panels render the committed one, so a suspending panel keeps the
|
|
13
|
+
* current panel on screen while the Root and the selected Tab show `data-pending`.
|
|
14
|
+
*/
|
|
15
|
+
export declare const Tabs: {
|
|
16
|
+
readonly Root: typeof TabsRoot;
|
|
17
|
+
readonly List: typeof TabsList;
|
|
18
|
+
readonly Tab: typeof TabsTab;
|
|
19
|
+
readonly Panel: typeof TabsPanel;
|
|
20
|
+
readonly Indicator: typeof TabsIndicator;
|
|
21
|
+
};
|
|
22
|
+
//#endregion
|
|
23
|
+
export type { TabsActivationMode, TabsIndicatorProps, TabsIndicatorState, TabsListProps, TabsListState, TabsOrientation, TabsPanelProps, TabsPanelState, TabsRootProps, TabsRootState, TabsTabProps, TabsTabState, TabsValueChangeDetails };
|
|
24
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/tabs/index.ts"],"mappings":";;;;;;;;;;;;;;qBAea;WACX,aAAI;WACJ,aAAI;WACJ,YAAG;WACH,cAAK;WACL,kBAAS"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { TabsIndicator } from "./tabs-indicator.mjs";
|
|
2
|
+
import { TabsList } from "./tabs-list.mjs";
|
|
3
|
+
import { TabsPanel } from "./tabs-panel.mjs";
|
|
4
|
+
import { TabsRoot } from "./tabs-root.mjs";
|
|
5
|
+
import { TabsTab } from "./tabs-tab.mjs";
|
|
6
|
+
//#region src/tabs/index.ts
|
|
7
|
+
/**
|
|
8
|
+
* Tabs (WAI-ARIA APG Tabs pattern): `Root` (div) > `List` (tablist) > `Tab` (button) and an
|
|
9
|
+
* optional `Indicator` (span), plus one `Panel` (tabpanel) per tab. Tabs and the Indicator render
|
|
10
|
+
* the optimistic selection; Panels render the committed one, so a suspending panel keeps the
|
|
11
|
+
* current panel on screen while the Root and the selected Tab show `data-pending`.
|
|
12
|
+
*/
|
|
13
|
+
const Tabs = {
|
|
14
|
+
Root: TabsRoot,
|
|
15
|
+
List: TabsList,
|
|
16
|
+
Tab: TabsTab,
|
|
17
|
+
Panel: TabsPanel,
|
|
18
|
+
Indicator: TabsIndicator
|
|
19
|
+
};
|
|
20
|
+
//#endregion
|
|
21
|
+
export { Tabs };
|
|
22
|
+
|
|
23
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/tabs/index.ts"],"sourcesContent":["// Ships the platform attribute typings with this subpath (conventions 7.4). Type-only: the\n// built JS has no import of it.\nimport \"../internal/platform-attributes.js\";\nimport { TabsIndicator } from \"./tabs-indicator.js\";\nimport { TabsList } from \"./tabs-list.js\";\nimport { TabsPanel } from \"./tabs-panel.js\";\nimport { TabsRoot } from \"./tabs-root.js\";\nimport { TabsTab } from \"./tabs-tab.js\";\n\n/**\n * Tabs (WAI-ARIA APG Tabs pattern): `Root` (div) > `List` (tablist) > `Tab` (button) and an\n * optional `Indicator` (span), plus one `Panel` (tabpanel) per tab. Tabs and the Indicator render\n * the optimistic selection; Panels render the committed one, so a suspending panel keeps the\n * current panel on screen while the Root and the selected Tab show `data-pending`.\n */\nexport const Tabs = {\n Root: TabsRoot,\n List: TabsList,\n Tab: TabsTab,\n Panel: TabsPanel,\n Indicator: TabsIndicator,\n} as const;\n\nexport type {\n TabsRootProps,\n TabsRootState,\n TabsValueChangeDetails,\n TabsOrientation,\n TabsActivationMode,\n} from \"./tabs-root.js\";\nexport type { TabsListProps, TabsListState } from \"./tabs-list.js\";\nexport type { TabsTabProps, TabsTabState } from \"./tabs-tab.js\";\nexport type { TabsPanelProps, TabsPanelState } from \"./tabs-panel.js\";\nexport type { TabsIndicatorProps, TabsIndicatorState } from \"./tabs-indicator.js\";\n"],"mappings":";;;;;;;;;;;;AAeA,MAAa,OAAO;CAClB,MAAM;CACN,MAAM;CACN,KAAK;CACL,OAAO;CACP,WAAW;AACb"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/tabs/tabs-context.d.ts
|
|
2
|
+
export type TabsOrientation = "horizontal" | "vertical";
|
|
3
|
+
export type TabsActivationMode = "automatic" | "manual";
|
|
4
|
+
/** Why a Tabs change was dispatched (design section 3.1). */
|
|
5
|
+
export type TabsChangeReason = "activate" | "platform";
|
|
6
|
+
//#endregion
|
|
7
|
+
//# sourceMappingURL=tabs-context.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tabs-context.d.mts","names":[],"sources":["../../src/tabs/tabs-context.ts"],"mappings":";YAGY;YACA;;YAGA"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { createContext, use } from "react";
|
|
3
|
+
//#region src/tabs/tabs-context.ts
|
|
4
|
+
const TabsContext = createContext(null);
|
|
5
|
+
function useTabsContext(part) {
|
|
6
|
+
const context = use(TabsContext);
|
|
7
|
+
if (context === null) throw new Error(`${part} must be rendered inside a Tabs.Root.`);
|
|
8
|
+
return context;
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
export { TabsContext, useTabsContext };
|
|
12
|
+
|
|
13
|
+
//# sourceMappingURL=tabs-context.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tabs-context.mjs","names":[],"sources":["../../src/tabs/tabs-context.ts"],"sourcesContent":["\"use client\";\nimport { createContext, use } from \"react\";\n\nexport type TabsOrientation = \"horizontal\" | \"vertical\";\nexport type TabsActivationMode = \"automatic\" | \"manual\";\n\n/** Why a Tabs change was dispatched (design section 3.1). */\nexport type TabsChangeReason = \"activate\" | \"platform\";\n\nexport interface TabsContextValue {\n readonly rootId: string;\n /** Committed value: Panels render this. */\n readonly committed: string | null;\n /** Optimistic value: Tabs and the Indicator render this. */\n readonly optimistic: string | null;\n readonly pending: boolean;\n readonly orientation: TabsOrientation;\n readonly activationMode: TabsActivationMode;\n readonly loop: boolean;\n readonly disabled: boolean;\n /** Dispatches a change. No-op if `value` is the optimistic value. Never checks `disabled`. */\n readonly select: (value: string, event: Event | undefined, reason: TabsChangeReason) => void;\n}\n\nexport const TabsContext = createContext<TabsContextValue | null>(null);\n\nexport function useTabsContext(part: string): TabsContextValue {\n const context = use(TabsContext);\n if (context === null) {\n throw new Error(`${part} must be rendered inside a Tabs.Root.`);\n }\n return context;\n}\n"],"mappings":";;;AAwBA,MAAa,cAAc,cAAuC,IAAI;AAEtE,SAAgB,eAAe,MAAgC;CAC7D,MAAM,UAAU,IAAI,WAAW;CAC/B,IAAI,YAAY,MACd,MAAM,IAAI,MAAM,GAAG,KAAK,sCAAsC;CAEhE,OAAO;AACT"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
//#region src/tabs/tabs-ids.ts
|
|
2
|
+
/**
|
|
3
|
+
* Id derivation for Tabs (design section 6). Every part derives its ids from the Root's `useId`
|
|
4
|
+
* and the tab `value`, so there is no registration pass and no extra render.
|
|
5
|
+
*
|
|
6
|
+
* No `"use client"` directive: pure functions, importable from anywhere (conventions 7.1).
|
|
7
|
+
*/
|
|
8
|
+
function escapeChar(char) {
|
|
9
|
+
return `_${(char.codePointAt(0) ?? 0).toString(16)}_`;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Makes `value` safe as both an HTML id fragment and a CSS dashed-ident suffix. Every character
|
|
13
|
+
* outside `[A-Za-z0-9-]` becomes `_<hex code point>_`. `_` itself is escaped too (`_5f_`), so the
|
|
14
|
+
* mapping is injective: `"a b"` and `"a_20_"` never share an id.
|
|
15
|
+
*/
|
|
16
|
+
function slug(value) {
|
|
17
|
+
return value.replace(/[^A-Za-z0-9-]/gu, escapeChar);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The Root id from `useId` (`_r_1_` on React 19.3) is already a valid id and CSS ident; anything
|
|
21
|
+
* else (a custom `identifierPrefix`) is escaped defensively for the anchor name, keeping `_`.
|
|
22
|
+
*/
|
|
23
|
+
function cssSafeRootId(rootId) {
|
|
24
|
+
return rootId.replace(/[^A-Za-z0-9_-]/gu, escapeChar);
|
|
25
|
+
}
|
|
26
|
+
function tabId(rootId, value) {
|
|
27
|
+
return `${rootId}-tab-${slug(value)}`;
|
|
28
|
+
}
|
|
29
|
+
function panelId(rootId, value) {
|
|
30
|
+
return `${rootId}-panel-${slug(value)}`;
|
|
31
|
+
}
|
|
32
|
+
/** `anchor-name` of a Tab, and `position-anchor` of the Indicator that points at it. */
|
|
33
|
+
function anchorName(rootId, value) {
|
|
34
|
+
return `--fsui-tab-${cssSafeRootId(rootId)}-tab-${slug(value)}`;
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
export { anchorName, panelId, slug, tabId };
|
|
38
|
+
|
|
39
|
+
//# sourceMappingURL=tabs-ids.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tabs-ids.mjs","names":[],"sources":["../../src/tabs/tabs-ids.ts"],"sourcesContent":["/**\n * Id derivation for Tabs (design section 6). Every part derives its ids from the Root's `useId`\n * and the tab `value`, so there is no registration pass and no extra render.\n *\n * No `\"use client\"` directive: pure functions, importable from anywhere (conventions 7.1).\n */\n\nfunction escapeChar(char: string): string {\n return `_${(char.codePointAt(0) ?? 0).toString(16)}_`;\n}\n\n/**\n * Makes `value` safe as both an HTML id fragment and a CSS dashed-ident suffix. Every character\n * outside `[A-Za-z0-9-]` becomes `_<hex code point>_`. `_` itself is escaped too (`_5f_`), so the\n * mapping is injective: `\"a b\"` and `\"a_20_\"` never share an id.\n */\nexport function slug(value: string): string {\n return value.replace(/[^A-Za-z0-9-]/gu, escapeChar);\n}\n\n/**\n * The Root id from `useId` (`_r_1_` on React 19.3) is already a valid id and CSS ident; anything\n * else (a custom `identifierPrefix`) is escaped defensively for the anchor name, keeping `_`.\n */\nfunction cssSafeRootId(rootId: string): string {\n return rootId.replace(/[^A-Za-z0-9_-]/gu, escapeChar);\n}\n\nexport function tabId(rootId: string, value: string): string {\n return `${rootId}-tab-${slug(value)}`;\n}\n\nexport function panelId(rootId: string, value: string): string {\n return `${rootId}-panel-${slug(value)}`;\n}\n\n/** `anchor-name` of a Tab, and `position-anchor` of the Indicator that points at it. */\nexport function anchorName(rootId: string, value: string): `--${string}` {\n return `--fsui-tab-${cssSafeRootId(rootId)}-tab-${slug(value)}`;\n}\n"],"mappings":";;;;;;;AAOA,SAAS,WAAW,MAAsB;CACxC,OAAO,KAAK,KAAK,YAAY,CAAC,KAAK,EAAA,CAAG,SAAS,EAAE,EAAE;AACrD;;;;;;AAOA,SAAgB,KAAK,OAAuB;CAC1C,OAAO,MAAM,QAAQ,mBAAmB,UAAU;AACpD;;;;;AAMA,SAAS,cAAc,QAAwB;CAC7C,OAAO,OAAO,QAAQ,oBAAoB,UAAU;AACtD;AAEA,SAAgB,MAAM,QAAgB,OAAuB;CAC3D,OAAO,GAAG,OAAO,OAAO,KAAK,KAAK;AACpC;AAEA,SAAgB,QAAQ,QAAgB,OAAuB;CAC7D,OAAO,GAAG,OAAO,SAAS,KAAK,KAAK;AACtC;;AAGA,SAAgB,WAAW,QAAgB,OAA8B;CACvE,OAAO,cAAc,cAAc,MAAM,EAAE,OAAO,KAAK,KAAK;AAC9D"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { RenderProp, StateValue } from "../internal/types.mjs";
|
|
2
|
+
import { TabsOrientation } from "./tabs-context.mjs";
|
|
3
|
+
import { CSSProperties, ComponentPropsWithRef, ReactNode } from "react";
|
|
4
|
+
//#region src/tabs/tabs-indicator.d.ts
|
|
5
|
+
export interface TabsIndicatorState {
|
|
6
|
+
/** Optimistic selected value, or `null` (the indicator is then `hidden`). */
|
|
7
|
+
readonly value: string | null;
|
|
8
|
+
readonly pending: boolean;
|
|
9
|
+
readonly orientation: TabsOrientation;
|
|
10
|
+
}
|
|
11
|
+
export interface TabsIndicatorProps extends Omit<ComponentPropsWithRef<"span">, "hidden" | "className" | "style" | "children"> {
|
|
12
|
+
className?: StateValue<string | undefined, TabsIndicatorState>;
|
|
13
|
+
style?: StateValue<CSSProperties | undefined, TabsIndicatorState>;
|
|
14
|
+
children?: StateValue<ReactNode, TabsIndicatorState>;
|
|
15
|
+
render?: RenderProp<ComponentPropsWithRef<"span">, TabsIndicatorState>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A decorative marker that follows the optimistic selection. Positioned by CSS Anchor
|
|
19
|
+
* Positioning: it sets `position-anchor` to the selected Tab's `anchor-name`; the consumer's CSS
|
|
20
|
+
* gives it `position: absolute` and `anchor()` insets, and a CSS `transition` on those insets
|
|
21
|
+
* slides it. It must come after the Tabs inside `Tabs.List` (an anchor must precede the
|
|
22
|
+
* positioned element in tree order). No `<ViewTransition>`: it changes only in optimistic (sync)
|
|
23
|
+
* commits, which never start a view transition (design 10.1).
|
|
24
|
+
*
|
|
25
|
+
* Owned keys (conventions 1.2): `aria-hidden`, `hidden`, `data-selected`, `data-pending`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function TabsIndicator({ className, style, children, render, ...rest }: TabsIndicatorProps): ReactNode;
|
|
28
|
+
export declare namespace TabsIndicator {
|
|
29
|
+
var displayName: string;
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
//# sourceMappingURL=tabs-indicator.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tabs-indicator.d.mts","names":[],"sources":["../../src/tabs/tabs-indicator.tsx"],"mappings":";;;;iBASiB;;WAEN;WACA;WACA,aAAa;;iBAGP,2BAA2B,KAC1C;EAGA,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;EACjC,SAAS,WAAW,+BAA+B;;;;;;;;;;;;wBAarC,gBAAgB,WAAW,OAAO,UAAU,WAAW,QAAQ,qBAAkB;yBAAjF"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { dataAttr } from "../internal/data-attr.mjs";
|
|
3
|
+
import { resolveStateValue, useRender } from "../internal/use-render.mjs";
|
|
4
|
+
import { useTabsContext } from "./tabs-context.mjs";
|
|
5
|
+
import { anchorName } from "./tabs-ids.mjs";
|
|
6
|
+
//#region src/tabs/tabs-indicator.tsx
|
|
7
|
+
/**
|
|
8
|
+
* A decorative marker that follows the optimistic selection. Positioned by CSS Anchor
|
|
9
|
+
* Positioning: it sets `position-anchor` to the selected Tab's `anchor-name`; the consumer's CSS
|
|
10
|
+
* gives it `position: absolute` and `anchor()` insets, and a CSS `transition` on those insets
|
|
11
|
+
* slides it. It must come after the Tabs inside `Tabs.List` (an anchor must precede the
|
|
12
|
+
* positioned element in tree order). No `<ViewTransition>`: it changes only in optimistic (sync)
|
|
13
|
+
* commits, which never start a view transition (design 10.1).
|
|
14
|
+
*
|
|
15
|
+
* Owned keys (conventions 1.2): `aria-hidden`, `hidden`, `data-selected`, `data-pending`.
|
|
16
|
+
*/
|
|
17
|
+
function TabsIndicator({ className, style, children, render, ...rest }) {
|
|
18
|
+
const ctx = useTabsContext("Tabs.Indicator");
|
|
19
|
+
const value = ctx.optimistic;
|
|
20
|
+
const state = {
|
|
21
|
+
value,
|
|
22
|
+
pending: ctx.pending,
|
|
23
|
+
orientation: ctx.orientation
|
|
24
|
+
};
|
|
25
|
+
const anchor = value === null ? void 0 : anchorName(ctx.rootId, value);
|
|
26
|
+
return useRender({
|
|
27
|
+
defaultElement: "span",
|
|
28
|
+
render,
|
|
29
|
+
state,
|
|
30
|
+
className,
|
|
31
|
+
style: (s) => ({
|
|
32
|
+
positionAnchor: anchor,
|
|
33
|
+
...resolveStateValue(style, s)
|
|
34
|
+
}),
|
|
35
|
+
children,
|
|
36
|
+
props: rest,
|
|
37
|
+
libraryProps: {
|
|
38
|
+
"aria-hidden": true,
|
|
39
|
+
hidden: value === null,
|
|
40
|
+
"data-selected": dataAttr(value !== null),
|
|
41
|
+
"data-pending": dataAttr(ctx.pending)
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
TabsIndicator.displayName = "TabsIndicator";
|
|
46
|
+
//#endregion
|
|
47
|
+
export { TabsIndicator };
|
|
48
|
+
|
|
49
|
+
//# sourceMappingURL=tabs-indicator.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tabs-indicator.mjs","names":[],"sources":["../../src/tabs/tabs-indicator.tsx"],"sourcesContent":["\"use client\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { resolveStateValue, useRender } from \"../internal/use-render.js\";\nimport { useTabsContext } from \"./tabs-context.js\";\nimport type { TabsOrientation } from \"./tabs-context.js\";\nimport { anchorName } from \"./tabs-ids.js\";\n\nexport interface TabsIndicatorState {\n /** Optimistic selected value, or `null` (the indicator is then `hidden`). */\n readonly value: string | null;\n readonly pending: boolean;\n readonly orientation: TabsOrientation;\n}\n\nexport interface TabsIndicatorProps extends Omit<\n ComponentPropsWithRef<\"span\">,\n \"hidden\" | \"className\" | \"style\" | \"children\"\n> {\n className?: StateValue<string | undefined, TabsIndicatorState>;\n style?: StateValue<CSSProperties | undefined, TabsIndicatorState>;\n children?: StateValue<ReactNode, TabsIndicatorState>;\n render?: RenderProp<ComponentPropsWithRef<\"span\">, TabsIndicatorState>;\n}\n\n/**\n * A decorative marker that follows the optimistic selection. Positioned by CSS Anchor\n * Positioning: it sets `position-anchor` to the selected Tab's `anchor-name`; the consumer's CSS\n * gives it `position: absolute` and `anchor()` insets, and a CSS `transition` on those insets\n * slides it. It must come after the Tabs inside `Tabs.List` (an anchor must precede the\n * positioned element in tree order). No `<ViewTransition>`: it changes only in optimistic (sync)\n * commits, which never start a view transition (design 10.1).\n *\n * Owned keys (conventions 1.2): `aria-hidden`, `hidden`, `data-selected`, `data-pending`.\n */\nexport function TabsIndicator({ className, style, children, render, ...rest }: TabsIndicatorProps) {\n const ctx = useTabsContext(\"Tabs.Indicator\");\n const value = ctx.optimistic;\n const state: TabsIndicatorState = {\n value,\n pending: ctx.pending,\n orientation: ctx.orientation,\n };\n const anchor = value === null ? undefined : anchorName(ctx.rootId, value);\n\n return useRender<ComponentPropsWithRef<\"span\">, TabsIndicatorState>({\n defaultElement: \"span\",\n render,\n state,\n className,\n // `position-anchor` sits under the consumer's style: the consumer wins.\n style: (s) => ({ positionAnchor: anchor, ...resolveStateValue(style, s) }),\n children,\n props: rest,\n libraryProps: {\n \"aria-hidden\": true,\n hidden: value === null,\n \"data-selected\": dataAttr(value !== null),\n \"data-pending\": dataAttr(ctx.pending),\n },\n });\n}\n\nTabsIndicator.displayName = \"TabsIndicator\";\n"],"mappings":";;;;;;;;;;;;;;;;AAoCA,SAAgB,cAAc,EAAE,WAAW,OAAO,UAAU,QAAQ,GAAG,QAA4B;CACjG,MAAM,MAAM,eAAe,gBAAgB;CAC3C,MAAM,QAAQ,IAAI;CAClB,MAAM,QAA4B;EAChC;EACA,SAAS,IAAI;EACb,aAAa,IAAI;CACnB;CACA,MAAM,SAAS,UAAU,OAAO,KAAA,IAAY,WAAW,IAAI,QAAQ,KAAK;CAExE,OAAO,UAA6D;EAClE,gBAAgB;EAChB;EACA;EACA;EAEA,QAAQ,OAAO;GAAE,gBAAgB;GAAQ,GAAG,kBAAkB,OAAO,CAAC;EAAE;EACxE;EACA,OAAO;EACP,cAAc;GACZ,eAAe;GACf,QAAQ,UAAU;GAClB,iBAAiB,SAAS,UAAU,IAAI;GACxC,gBAAgB,SAAS,IAAI,OAAO;EACtC;CACF,CAAC;AACH;AAEA,cAAc,cAAc"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { RenderProp, StateValue } from "../internal/types.mjs";
|
|
2
|
+
import { TabsOrientation } from "./tabs-context.mjs";
|
|
3
|
+
import { CSSProperties, ComponentPropsWithRef, ReactNode } from "react";
|
|
4
|
+
//#region src/tabs/tabs-list.d.ts
|
|
5
|
+
export interface TabsListState {
|
|
6
|
+
readonly orientation: TabsOrientation;
|
|
7
|
+
readonly disabled: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface TabsListProps extends Omit<ComponentPropsWithRef<"div">, "onKeyDown" | "onFocus" | "className" | "style" | "children"> {
|
|
10
|
+
/**
|
|
11
|
+
* Render `focusgroup="tablist …"` and, where the browser implements it, let the browser handle
|
|
12
|
+
* arrow keys, Home and End. Chromium 150+ only; elsewhere the JS path stays active.
|
|
13
|
+
* Default `false`.
|
|
14
|
+
*/
|
|
15
|
+
experimental_focusgroup?: boolean;
|
|
16
|
+
className?: StateValue<string | undefined, TabsListState>;
|
|
17
|
+
style?: StateValue<CSSProperties | undefined, TabsListState>;
|
|
18
|
+
children?: StateValue<ReactNode, TabsListState>;
|
|
19
|
+
render?: RenderProp<ComponentPropsWithRef<"div">, TabsListState>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The `role="tablist"` container. Owns arrow-key / Home / End navigation between Tabs: it only
|
|
23
|
+
* moves focus; automatic activation happens in the Tab's focus handler.
|
|
24
|
+
*
|
|
25
|
+
* Owned keys (a `render` element cannot override them, conventions 1.2): `role`,
|
|
26
|
+
* `aria-orientation`, `data-orientation`, `data-disabled`, `tabIndex`, `onKeyDown`, `onFocus`,
|
|
27
|
+
* and `focusgroup` when `experimental_focusgroup` is on.
|
|
28
|
+
*/
|
|
29
|
+
export declare function TabsList({ experimental_focusgroup, className, style, children, render, ...rest }: TabsListProps): ReactNode;
|
|
30
|
+
export declare namespace TabsList {
|
|
31
|
+
var displayName: string;
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
//# sourceMappingURL=tabs-list.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tabs-list.d.mts","names":[],"sources":["../../src/tabs/tabs-list.tsx"],"mappings":";;;;iBAgBiB;WACN,aAAa;WACb;;iBAGM,sBAAsB,KACrC;;;;;;EAQA;EACA,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;EACjC,SAAS,WAAW,8BAA8B;;;;;;;;;;wBA4CpC,WACd,yBACA,WACA,OACA,UACA,WACG,QACF,gBAAa;yBAPA"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { dataAttr } from "../internal/data-attr.mjs";
|
|
3
|
+
import { useRender } from "../internal/use-render.mjs";
|
|
4
|
+
import { useDevWarning } from "../internal/use-dev-warning.mjs";
|
|
5
|
+
import { useTabsContext } from "./tabs-context.mjs";
|
|
6
|
+
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
7
|
+
//#region src/tabs/tabs-list.tsx
|
|
8
|
+
function resolveKey(key, orientation, rtl) {
|
|
9
|
+
switch (key) {
|
|
10
|
+
case "Home": return "first";
|
|
11
|
+
case "End": return "last";
|
|
12
|
+
case "ArrowRight": return orientation === "horizontal" ? rtl ? "prev" : "next" : null;
|
|
13
|
+
case "ArrowLeft": return orientation === "horizontal" ? rtl ? "next" : "prev" : null;
|
|
14
|
+
case "ArrowDown": return orientation === "vertical" ? "next" : null;
|
|
15
|
+
case "ArrowUp": return orientation === "vertical" ? "prev" : null;
|
|
16
|
+
default: return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** The tabs that belong to `list` (not to a nested tablist), in DOM order. Disabled included. */
|
|
20
|
+
function tabsOf(list) {
|
|
21
|
+
return Array.from(list.querySelectorAll("[role=\"tab\"]")).filter((tab) => tab.closest("[role=\"tablist\"]") === list);
|
|
22
|
+
}
|
|
23
|
+
function focusTab(tab) {
|
|
24
|
+
tab.focus({ focusVisible: true });
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The `role="tablist"` container. Owns arrow-key / Home / End navigation between Tabs: it only
|
|
28
|
+
* moves focus; automatic activation happens in the Tab's focus handler.
|
|
29
|
+
*
|
|
30
|
+
* Owned keys (a `render` element cannot override them, conventions 1.2): `role`,
|
|
31
|
+
* `aria-orientation`, `data-orientation`, `data-disabled`, `tabIndex`, `onKeyDown`, `onFocus`,
|
|
32
|
+
* and `focusgroup` when `experimental_focusgroup` is on.
|
|
33
|
+
*/
|
|
34
|
+
function TabsList({ experimental_focusgroup = false, className, style, children, render, ...rest }) {
|
|
35
|
+
const { orientation, loop, disabled, optimistic } = useTabsContext("Tabs.List");
|
|
36
|
+
const nativeFocusgroup = useRef(false);
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
nativeFocusgroup.current = experimental_focusgroup && "focusgroup" in HTMLElement.prototype;
|
|
39
|
+
}, [experimental_focusgroup]);
|
|
40
|
+
const listRef = useRef(null);
|
|
41
|
+
const [unmatched, setUnmatched] = useState(false);
|
|
42
|
+
useLayoutEffect(() => {
|
|
43
|
+
const list = listRef.current;
|
|
44
|
+
const next = optimistic !== null && list !== null && !tabsOf(list).some((tab) => tab.getAttribute("tabindex") === "0");
|
|
45
|
+
if (next !== unmatched) setUnmatched(next);
|
|
46
|
+
}, [
|
|
47
|
+
optimistic,
|
|
48
|
+
children,
|
|
49
|
+
unmatched
|
|
50
|
+
]);
|
|
51
|
+
useDevWarning(unmatched, `Tabs: the value ${JSON.stringify(optimistic)} matches no Tabs.Tab. No tab is selected; the tablist itself is the tab stop. Pass null to select nothing on purpose.`);
|
|
52
|
+
const listIsTabStop = optimistic === null || unmatched;
|
|
53
|
+
const state = {
|
|
54
|
+
orientation,
|
|
55
|
+
disabled
|
|
56
|
+
};
|
|
57
|
+
const onKeyDown = (event) => {
|
|
58
|
+
if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey) return;
|
|
59
|
+
const list = event.currentTarget;
|
|
60
|
+
const target = event.target;
|
|
61
|
+
if (target.getAttribute("role") !== "tab" || target.closest("[role=\"tablist\"]") !== list) return;
|
|
62
|
+
const rtl = orientation === "horizontal" && getComputedStyle(list).direction === "rtl";
|
|
63
|
+
const move = resolveKey(event.key, orientation, rtl);
|
|
64
|
+
if (move === null) return;
|
|
65
|
+
if (nativeFocusgroup.current) return;
|
|
66
|
+
event.preventDefault();
|
|
67
|
+
const tabs = tabsOf(list);
|
|
68
|
+
if (tabs.length === 0) return;
|
|
69
|
+
const index = tabs.indexOf(target);
|
|
70
|
+
const last = tabs.length - 1;
|
|
71
|
+
let nextIndex;
|
|
72
|
+
switch (move) {
|
|
73
|
+
case "first":
|
|
74
|
+
nextIndex = 0;
|
|
75
|
+
break;
|
|
76
|
+
case "last":
|
|
77
|
+
nextIndex = last;
|
|
78
|
+
break;
|
|
79
|
+
case "next":
|
|
80
|
+
nextIndex = index >= last ? loop ? 0 : last : index + 1;
|
|
81
|
+
break;
|
|
82
|
+
case "prev": nextIndex = index <= 0 ? loop ? last : 0 : index - 1;
|
|
83
|
+
}
|
|
84
|
+
const next = tabs[nextIndex];
|
|
85
|
+
if (next !== void 0 && next !== target) focusTab(next);
|
|
86
|
+
};
|
|
87
|
+
const onFocus = (event) => {
|
|
88
|
+
const list = event.currentTarget;
|
|
89
|
+
if (event.target !== list || !listIsTabStop) return;
|
|
90
|
+
const from = event.relatedTarget;
|
|
91
|
+
if (from instanceof Node && list.contains(from)) return;
|
|
92
|
+
const tabs = tabsOf(list);
|
|
93
|
+
const first = tabs.find((tab) => tab.getAttribute("aria-disabled") !== "true") ?? tabs[0];
|
|
94
|
+
if (first !== void 0) focusTab(first);
|
|
95
|
+
};
|
|
96
|
+
const focusgroup = experimental_focusgroup ? `tablist ${orientation === "vertical" ? "block" : "inline"} ${loop ? "wrap" : "nowrap"}` : void 0;
|
|
97
|
+
return useRender({
|
|
98
|
+
defaultElement: "div",
|
|
99
|
+
render,
|
|
100
|
+
state,
|
|
101
|
+
className,
|
|
102
|
+
style,
|
|
103
|
+
children,
|
|
104
|
+
props: rest,
|
|
105
|
+
libraryProps: {
|
|
106
|
+
role: "tablist",
|
|
107
|
+
"aria-orientation": orientation,
|
|
108
|
+
"data-orientation": orientation,
|
|
109
|
+
"data-disabled": dataAttr(disabled),
|
|
110
|
+
tabIndex: listIsTabStop ? 0 : void 0,
|
|
111
|
+
...focusgroup !== void 0 && { focusgroup },
|
|
112
|
+
onKeyDown,
|
|
113
|
+
onFocus
|
|
114
|
+
},
|
|
115
|
+
ref: listRef
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
TabsList.displayName = "TabsList";
|
|
119
|
+
//#endregion
|
|
120
|
+
export { TabsList };
|
|
121
|
+
|
|
122
|
+
//# sourceMappingURL=tabs-list.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tabs-list.mjs","names":[],"sources":["../../src/tabs/tabs-list.tsx"],"sourcesContent":["\"use client\";\nimport { useEffect, useLayoutEffect, useRef, useState } from \"react\";\nimport type {\n ComponentPropsWithRef,\n CSSProperties,\n FocusEvent,\n KeyboardEvent,\n ReactNode,\n} from \"react\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { useTabsContext } from \"./tabs-context.js\";\nimport type { TabsOrientation } from \"./tabs-context.js\";\n\nexport interface TabsListState {\n readonly orientation: TabsOrientation;\n readonly disabled: boolean;\n}\n\nexport interface TabsListProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n \"onKeyDown\" | \"onFocus\" | \"className\" | \"style\" | \"children\"\n> {\n /**\n * Render `focusgroup=\"tablist …\"` and, where the browser implements it, let the browser handle\n * arrow keys, Home and End. Chromium 150+ only; elsewhere the JS path stays active.\n * Default `false`.\n */\n experimental_focusgroup?: boolean;\n className?: StateValue<string | undefined, TabsListState>;\n style?: StateValue<CSSProperties | undefined, TabsListState>;\n children?: StateValue<ReactNode, TabsListState>;\n render?: RenderProp<ComponentPropsWithRef<\"div\">, TabsListState>;\n}\n\ntype Move = \"next\" | \"prev\" | \"first\" | \"last\";\n\nfunction resolveKey(key: string, orientation: TabsOrientation, rtl: boolean): Move | null {\n switch (key) {\n case \"Home\":\n return \"first\";\n case \"End\":\n return \"last\";\n case \"ArrowRight\":\n return orientation === \"horizontal\" ? (rtl ? \"prev\" : \"next\") : null;\n case \"ArrowLeft\":\n return orientation === \"horizontal\" ? (rtl ? \"next\" : \"prev\") : null;\n case \"ArrowDown\":\n return orientation === \"vertical\" ? \"next\" : null;\n case \"ArrowUp\":\n return orientation === \"vertical\" ? \"prev\" : null;\n default:\n return null;\n }\n}\n\n/** The tabs that belong to `list` (not to a nested tablist), in DOM order. Disabled included. */\nfunction tabsOf(list: Element): HTMLElement[] {\n return Array.from(list.querySelectorAll<HTMLElement>('[role=\"tab\"]')).filter(\n (tab) => tab.closest('[role=\"tablist\"]') === list,\n );\n}\n\nfunction focusTab(tab: HTMLElement): void {\n // `focusVisible` shows the ring even where the engine's heuristic would not (survey: adopt).\n tab.focus({ focusVisible: true } as FocusOptions);\n}\n\n/**\n * The `role=\"tablist\"` container. Owns arrow-key / Home / End navigation between Tabs: it only\n * moves focus; automatic activation happens in the Tab's focus handler.\n *\n * Owned keys (a `render` element cannot override them, conventions 1.2): `role`,\n * `aria-orientation`, `data-orientation`, `data-disabled`, `tabIndex`, `onKeyDown`, `onFocus`,\n * and `focusgroup` when `experimental_focusgroup` is on.\n */\nexport function TabsList({\n experimental_focusgroup = false,\n className,\n style,\n children,\n render,\n ...rest\n}: TabsListProps) {\n const ctx = useTabsContext(\"Tabs.List\");\n const { orientation, loop, disabled, optimistic } = ctx;\n\n // Feature detection only when the flag is on, and only in an effect (conventions 8): markup\n // never differs between server and client.\n const nativeFocusgroup = useRef(false);\n useEffect(() => {\n nativeFocusgroup.current = experimental_focusgroup && \"focusgroup\" in HTMLElement.prototype;\n }, [experimental_focusgroup]);\n\n // A value that matches no Tab (a controlled `value` for a route without a tab, design 11.5)\n // leaves no tab with tabindex=0, so the List takes over as the tab stop exactly as for `null`.\n // Read from the DOM (Tabs register nowhere) whenever the value or the List's children change; a\n // Tab added or removed by a component below that re-renders on its own is picked up on the\n // List's next render. The state only changes when the answer does.\n const listRef = useRef<HTMLElement | null>(null);\n const [unmatched, setUnmatched] = useState(false);\n useLayoutEffect(() => {\n const list = listRef.current;\n const next =\n optimistic !== null &&\n list !== null &&\n !tabsOf(list).some((tab) => tab.getAttribute(\"tabindex\") === \"0\");\n if (next !== unmatched) setUnmatched(next);\n }, [optimistic, children, unmatched]);\n useDevWarning(\n unmatched,\n `Tabs: the value ${JSON.stringify(optimistic)} matches no Tabs.Tab. No tab is selected; the ` +\n \"tablist itself is the tab stop. Pass null to select nothing on purpose.\",\n );\n const listIsTabStop = optimistic === null || unmatched;\n\n const state: TabsListState = { orientation, disabled };\n\n const onKeyDown = (event: KeyboardEvent<HTMLElement>) => {\n if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey) return;\n const list = event.currentTarget;\n const target = event.target as Element;\n if (target.getAttribute(\"role\") !== \"tab\" || target.closest('[role=\"tablist\"]') !== list) {\n return;\n }\n const rtl = orientation === \"horizontal\" && getComputedStyle(list).direction === \"rtl\";\n const move = resolveKey(event.key, orientation, rtl);\n if (move === null) return;\n // The browser moves focus itself where `focusgroup` is implemented.\n if (nativeFocusgroup.current) return;\n\n event.preventDefault();\n const tabs = tabsOf(list);\n if (tabs.length === 0) return;\n const index = tabs.indexOf(target as HTMLElement);\n const last = tabs.length - 1;\n let nextIndex: number;\n switch (move) {\n case \"first\":\n nextIndex = 0;\n break;\n case \"last\":\n nextIndex = last;\n break;\n case \"next\":\n nextIndex = index >= last ? (loop ? 0 : last) : index + 1;\n break;\n case \"prev\":\n nextIndex = index <= 0 ? (loop ? last : 0) : index - 1;\n break;\n }\n const next = tabs[nextIndex];\n if (next !== undefined && next !== target) focusTab(next);\n };\n\n // With no selected tab (value null or matching no Tab) no Tab has tabindex=0, so the list\n // itself is the tab stop and hands focus to the first enabled tab (design section 8). Only when\n // focus arrives from outside the list: Shift+Tab from a tab inside would otherwise bounce\n // straight back. The redirect never selects: the Tab only dispatches on focus that moves\n // between tabs.\n const onFocus = (event: FocusEvent<HTMLElement>) => {\n const list = event.currentTarget;\n if (event.target !== list || !listIsTabStop) return;\n const from = event.relatedTarget;\n if (from instanceof Node && list.contains(from)) return;\n const tabs = tabsOf(list);\n const first = tabs.find((tab) => tab.getAttribute(\"aria-disabled\") !== \"true\") ?? tabs[0];\n if (first !== undefined) focusTab(first);\n };\n\n const focusgroup = experimental_focusgroup\n ? `tablist ${orientation === \"vertical\" ? \"block\" : \"inline\"} ${loop ? \"wrap\" : \"nowrap\"}`\n : undefined;\n\n return useRender<ComponentPropsWithRef<\"div\">, TabsListState>({\n defaultElement: \"div\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps: {\n role: \"tablist\",\n \"aria-orientation\": orientation,\n \"data-orientation\": orientation,\n \"data-disabled\": dataAttr(disabled),\n tabIndex: listIsTabStop ? 0 : undefined,\n // Owned only while the flag is on; otherwise the key is omitted, not `undefined`, so it is not\n // owned (conventions 1.2).\n ...(focusgroup !== undefined && { focusgroup }),\n onKeyDown,\n onFocus,\n },\n ref: listRef,\n });\n}\n\nTabsList.displayName = \"TabsList\";\n"],"mappings":";;;;;;;AAuCA,SAAS,WAAW,KAAa,aAA8B,KAA2B;CACxF,QAAQ,KAAR;EACE,KAAK,QACH,OAAO;EACT,KAAK,OACH,OAAO;EACT,KAAK,cACH,OAAO,gBAAgB,eAAgB,MAAM,SAAS,SAAU;EAClE,KAAK,aACH,OAAO,gBAAgB,eAAgB,MAAM,SAAS,SAAU;EAClE,KAAK,aACH,OAAO,gBAAgB,aAAa,SAAS;EAC/C,KAAK,WACH,OAAO,gBAAgB,aAAa,SAAS;EAC/C,SACE,OAAO;CACX;AACF;;AAGA,SAAS,OAAO,MAA8B;CAC5C,OAAO,MAAM,KAAK,KAAK,iBAA8B,gBAAc,CAAC,CAAC,CAAC,QACnE,QAAQ,IAAI,QAAQ,oBAAkB,MAAM,IAC/C;AACF;AAEA,SAAS,SAAS,KAAwB;CAExC,IAAI,MAAM,EAAE,cAAc,KAAK,CAAiB;AAClD;;;;;;;;;AAUA,SAAgB,SAAS,EACvB,0BAA0B,OAC1B,WACA,OACA,UACA,QACA,GAAG,QACa;CAEhB,MAAM,EAAE,aAAa,MAAM,UAAU,eADzB,eAAe,WAC2B;CAItD,MAAM,mBAAmB,OAAO,KAAK;CACrC,gBAAgB;EACd,iBAAiB,UAAU,2BAA2B,gBAAgB,YAAY;CACpF,GAAG,CAAC,uBAAuB,CAAC;CAO5B,MAAM,UAAU,OAA2B,IAAI;CAC/C,MAAM,CAAC,WAAW,gBAAgB,SAAS,KAAK;CAChD,sBAAsB;EACpB,MAAM,OAAO,QAAQ;EACrB,MAAM,OACJ,eAAe,QACf,SAAS,QACT,CAAC,OAAO,IAAI,CAAC,CAAC,MAAM,QAAQ,IAAI,aAAa,UAAU,MAAM,GAAG;EAClE,IAAI,SAAS,WAAW,aAAa,IAAI;CAC3C,GAAG;EAAC;EAAY;EAAU;CAAS,CAAC;CACpC,cACE,WACA,mBAAmB,KAAK,UAAU,UAAU,EAAE,sHAEhD;CACA,MAAM,gBAAgB,eAAe,QAAQ;CAE7C,MAAM,QAAuB;EAAE;EAAa;CAAS;CAErD,MAAM,aAAa,UAAsC;EACvD,IAAI,MAAM,oBAAoB,MAAM,UAAU,MAAM,WAAW,MAAM,SAAS;EAC9E,MAAM,OAAO,MAAM;EACnB,MAAM,SAAS,MAAM;EACrB,IAAI,OAAO,aAAa,MAAM,MAAM,SAAS,OAAO,QAAQ,oBAAkB,MAAM,MAClF;EAEF,MAAM,MAAM,gBAAgB,gBAAgB,iBAAiB,IAAI,CAAC,CAAC,cAAc;EACjF,MAAM,OAAO,WAAW,MAAM,KAAK,aAAa,GAAG;EACnD,IAAI,SAAS,MAAM;EAEnB,IAAI,iBAAiB,SAAS;EAE9B,MAAM,eAAe;EACrB,MAAM,OAAO,OAAO,IAAI;EACxB,IAAI,KAAK,WAAW,GAAG;EACvB,MAAM,QAAQ,KAAK,QAAQ,MAAqB;EAChD,MAAM,OAAO,KAAK,SAAS;EAC3B,IAAI;EACJ,QAAQ,MAAR;GACE,KAAK;IACH,YAAY;IACZ;GACF,KAAK;IACH,YAAY;IACZ;GACF,KAAK;IACH,YAAY,SAAS,OAAQ,OAAO,IAAI,OAAQ,QAAQ;IACxD;GACF,KAAK,QACH,YAAY,SAAS,IAAK,OAAO,OAAO,IAAK,QAAQ;EAEzD;EACA,MAAM,OAAO,KAAK;EAClB,IAAI,SAAS,KAAA,KAAa,SAAS,QAAQ,SAAS,IAAI;CAC1D;CAOA,MAAM,WAAW,UAAmC;EAClD,MAAM,OAAO,MAAM;EACnB,IAAI,MAAM,WAAW,QAAQ,CAAC,eAAe;EAC7C,MAAM,OAAO,MAAM;EACnB,IAAI,gBAAgB,QAAQ,KAAK,SAAS,IAAI,GAAG;EACjD,MAAM,OAAO,OAAO,IAAI;EACxB,MAAM,QAAQ,KAAK,MAAM,QAAQ,IAAI,aAAa,eAAe,MAAM,MAAM,KAAK,KAAK;EACvF,IAAI,UAAU,KAAA,GAAW,SAAS,KAAK;CACzC;CAEA,MAAM,aAAa,0BACf,WAAW,gBAAgB,aAAa,UAAU,SAAS,GAAG,OAAO,SAAS,aAC9E,KAAA;CAEJ,OAAO,UAAuD;EAC5D,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA,OAAO;EACP,cAAc;GACZ,MAAM;GACN,oBAAoB;GACpB,oBAAoB;GACpB,iBAAiB,SAAS,QAAQ;GAClC,UAAU,gBAAgB,IAAI,KAAA;GAG9B,GAAI,eAAe,KAAA,KAAa,EAAE,WAAW;GAC7C;GACA;EACF;EACA,KAAK;CACP,CAAC;AACH;AAEA,SAAS,cAAc"}
|