@funstack/ui 0.0.0-stage → 0.1.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +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/checkbox-group/checkbox-group-context.mjs +24 -0
- package/dist/checkbox-group/checkbox-group-context.mjs.map +1 -0
- package/dist/checkbox-group/checkbox-group-item.d.mts +55 -0
- package/dist/checkbox-group/checkbox-group-item.d.mts.map +1 -0
- package/dist/checkbox-group/checkbox-group-item.mjs +90 -0
- package/dist/checkbox-group/checkbox-group-item.mjs.map +1 -0
- package/dist/checkbox-group/checkbox-group-label.d.mts +21 -0
- package/dist/checkbox-group/checkbox-group-label.d.mts.map +1 -0
- package/dist/checkbox-group/checkbox-group-label.mjs +30 -0
- package/dist/checkbox-group/checkbox-group-label.mjs.map +1 -0
- package/dist/checkbox-group/checkbox-group-parent.d.mts +68 -0
- package/dist/checkbox-group/checkbox-group-parent.d.mts.map +1 -0
- package/dist/checkbox-group/checkbox-group-parent.mjs +145 -0
- package/dist/checkbox-group/checkbox-group-parent.mjs.map +1 -0
- package/dist/checkbox-group/checkbox-group-root.d.mts +100 -0
- package/dist/checkbox-group/checkbox-group-root.d.mts.map +1 -0
- package/dist/checkbox-group/checkbox-group-root.mjs +291 -0
- package/dist/checkbox-group/checkbox-group-root.mjs.map +1 -0
- package/dist/checkbox-group/index.d.mts +23 -0
- package/dist/checkbox-group/index.d.mts.map +1 -0
- package/dist/checkbox-group/index.mjs +23 -0
- package/dist/checkbox-group/index.mjs.map +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 +367 -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 +113 -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 +60 -0
- package/dist/index.mjs +12 -0
- package/dist/internal/action-details.mjs +47 -0
- package/dist/internal/action-details.mjs.map +1 -0
- package/dist/internal/anchor-position.d.mts +7 -0
- package/dist/internal/anchor-position.d.mts.map +1 -0
- package/dist/internal/anchor-position.mjs +16 -0
- package/dist/internal/anchor-position.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 +55 -0
- package/dist/internal/platform-attributes.d.mts.map +1 -0
- package/dist/internal/sync-presence.mjs +19 -0
- package/dist/internal/sync-presence.mjs.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-action-trigger.mjs +58 -0
- package/dist/internal/use-action-trigger.mjs.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-delayed-pair.mjs +40 -0
- package/dist/internal/use-delayed-pair.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-exit-animation.mjs +55 -0
- package/dist/internal/use-exit-animation.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-popover-popup.mjs +274 -0
- package/dist/internal/use-popover-popup.mjs.map +1 -0
- package/dist/internal/use-popover-root.d.mts +2 -0
- package/dist/internal/use-popover-root.mjs +112 -0
- package/dist/internal/use-popover-root.mjs.map +1 -0
- package/dist/internal/use-popover-trigger.mjs +86 -0
- package/dist/internal/use-popover-trigger.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/menu/index.d.mts +34 -0
- package/dist/menu/index.d.mts.map +1 -0
- package/dist/menu/index.mjs +34 -0
- package/dist/menu/index.mjs.map +1 -0
- package/dist/menu/menu-checkbox-item.d.mts +42 -0
- package/dist/menu/menu-checkbox-item.d.mts.map +1 -0
- package/dist/menu/menu-checkbox-item.mjs +71 -0
- package/dist/menu/menu-checkbox-item.mjs.map +1 -0
- package/dist/menu/menu-context.d.mts +1 -0
- package/dist/menu/menu-context.mjs +28 -0
- package/dist/menu/menu-context.mjs.map +1 -0
- package/dist/menu/menu-dom.mjs +37 -0
- package/dist/menu/menu-dom.mjs.map +1 -0
- package/dist/menu/menu-group.d.mts +48 -0
- package/dist/menu/menu-group.d.mts.map +1 -0
- package/dist/menu/menu-group.mjs +69 -0
- package/dist/menu/menu-group.mjs.map +1 -0
- package/dist/menu/menu-hover.mjs +29 -0
- package/dist/menu/menu-hover.mjs.map +1 -0
- package/dist/menu/menu-item.d.mts +45 -0
- package/dist/menu/menu-item.d.mts.map +1 -0
- package/dist/menu/menu-item.mjs +54 -0
- package/dist/menu/menu-item.mjs.map +1 -0
- package/dist/menu/menu-popup.d.mts +69 -0
- package/dist/menu/menu-popup.d.mts.map +1 -0
- package/dist/menu/menu-popup.mjs +207 -0
- package/dist/menu/menu-popup.mjs.map +1 -0
- package/dist/menu/menu-radio-group.d.mts +37 -0
- package/dist/menu/menu-radio-group.d.mts.map +1 -0
- package/dist/menu/menu-radio-group.mjs +72 -0
- package/dist/menu/menu-radio-group.mjs.map +1 -0
- package/dist/menu/menu-radio-item.d.mts +33 -0
- package/dist/menu/menu-radio-item.d.mts.map +1 -0
- package/dist/menu/menu-radio-item.mjs +62 -0
- package/dist/menu/menu-radio-item.mjs.map +1 -0
- package/dist/menu/menu-root.d.mts +54 -0
- package/dist/menu/menu-root.d.mts.map +1 -0
- package/dist/menu/menu-root.mjs +102 -0
- package/dist/menu/menu-root.mjs.map +1 -0
- package/dist/menu/menu-submenu-trigger.d.mts +31 -0
- package/dist/menu/menu-submenu-trigger.d.mts.map +1 -0
- package/dist/menu/menu-submenu-trigger.mjs +92 -0
- package/dist/menu/menu-submenu-trigger.mjs.map +1 -0
- package/dist/menu/menu-trigger.d.mts +36 -0
- package/dist/menu/menu-trigger.d.mts.map +1 -0
- package/dist/menu/menu-trigger.mjs +81 -0
- package/dist/menu/menu-trigger.mjs.map +1 -0
- package/dist/menu/use-menu-item.mjs +54 -0
- package/dist/menu/use-menu-item.mjs.map +1 -0
- package/dist/popover/index.d.mts +28 -0
- package/dist/popover/index.d.mts.map +1 -0
- package/dist/popover/index.mjs +28 -0
- package/dist/popover/index.mjs.map +1 -0
- package/dist/popover/popover-close.d.mts +41 -0
- package/dist/popover/popover-close.d.mts.map +1 -0
- package/dist/popover/popover-close.mjs +90 -0
- package/dist/popover/popover-close.mjs.map +1 -0
- package/dist/popover/popover-context.mjs +26 -0
- package/dist/popover/popover-context.mjs.map +1 -0
- package/dist/popover/popover-description.d.mts +22 -0
- package/dist/popover/popover-description.d.mts.map +1 -0
- package/dist/popover/popover-description.mjs +28 -0
- package/dist/popover/popover-description.mjs.map +1 -0
- package/dist/popover/popover-popup.d.mts +82 -0
- package/dist/popover/popover-popup.d.mts.map +1 -0
- package/dist/popover/popover-popup.mjs +93 -0
- package/dist/popover/popover-popup.mjs.map +1 -0
- package/dist/popover/popover-root.d.mts +64 -0
- package/dist/popover/popover-root.d.mts.map +1 -0
- package/dist/popover/popover-root.mjs +39 -0
- package/dist/popover/popover-root.mjs.map +1 -0
- package/dist/popover/popover-title.d.mts +23 -0
- package/dist/popover/popover-title.d.mts.map +1 -0
- package/dist/popover/popover-title.mjs +28 -0
- package/dist/popover/popover-title.mjs.map +1 -0
- package/dist/popover/popover-trigger.d.mts +37 -0
- package/dist/popover/popover-trigger.d.mts.map +1 -0
- package/dist/popover/popover-trigger.mjs +40 -0
- package/dist/popover/popover-trigger.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 +83 -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 +14 -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 +130 -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 +89 -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 +52 -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 +119 -0
- package/dist/tooltip/use-tooltip-interaction.mjs.map +1 -0
- package/package.json +73 -3
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { createActionDetails } from "../internal/action-details.mjs";
|
|
3
|
+
import { dataAttr } from "../internal/data-attr.mjs";
|
|
4
|
+
import { transitionTypes } from "../internal/transition-types.mjs";
|
|
5
|
+
import { useControllableAction } from "../internal/use-controllable-action.mjs";
|
|
6
|
+
import { useRender } from "../internal/use-render.mjs";
|
|
7
|
+
import { useDevWarning } from "../internal/use-dev-warning.mjs";
|
|
8
|
+
import { TabsContext } from "./tabs-context.mjs";
|
|
9
|
+
import { tabId } from "./tabs-ids.mjs";
|
|
10
|
+
import { useId, useRef } from "react";
|
|
11
|
+
import { jsx } from "react/jsx-runtime";
|
|
12
|
+
//#region src/tabs/tabs-root.tsx
|
|
13
|
+
/** Looks up an element by id in the tree that contains `node` (document or shadow root). */
|
|
14
|
+
function findById(node, id) {
|
|
15
|
+
const root = node?.getRootNode() ?? (typeof document === "undefined" ? null : document);
|
|
16
|
+
if (root !== null && "getElementById" in root) return root.getElementById(id);
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Owns the Tabs state (`value`), the transition, the ids and the context. Renders a `div` with
|
|
21
|
+
* `data-pending` while a selection is in flight (including a suspending panel's wait).
|
|
22
|
+
*
|
|
23
|
+
* Owned keys (conventions 1.2): `data-pending`, `data-orientation`, `data-disabled`.
|
|
24
|
+
*/
|
|
25
|
+
function TabsRoot({ value, defaultValue, valueChangeAction, orientation = "horizontal", activationMode = "automatic", loop = true, disabled = false, className, style, children, render, ...rest }) {
|
|
26
|
+
const rootId = useId();
|
|
27
|
+
const rootRef = useRef(null);
|
|
28
|
+
const { committed, optimistic, pending, dispatch } = useControllableAction({
|
|
29
|
+
controlled: value,
|
|
30
|
+
defaultValue,
|
|
31
|
+
initialValue: null,
|
|
32
|
+
action: valueChangeAction === void 0 ? void 0 : (next, details) => valueChangeAction(next, details),
|
|
33
|
+
transitionTypes: (next, prev) => {
|
|
34
|
+
if (next === null || prev === null || next === prev) return transitionTypes("tabs");
|
|
35
|
+
const prevTab = findById(rootRef.current, tabId(rootId, prev));
|
|
36
|
+
const nextTab = findById(rootRef.current, tabId(rootId, next));
|
|
37
|
+
if (prevTab === null || nextTab === null) return transitionTypes("tabs");
|
|
38
|
+
const following = prevTab.compareDocumentPosition(nextTab) & Node.DOCUMENT_POSITION_FOLLOWING;
|
|
39
|
+
return transitionTypes("tabs", following ? "next" : "prev");
|
|
40
|
+
},
|
|
41
|
+
warn: {
|
|
42
|
+
component: "Tabs.Root",
|
|
43
|
+
prop: "value"
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
useDevWarning(value === void 0 && defaultValue === void 0, "Tabs.Root: neither `value` nor `defaultValue` was passed, so no tab is selected and no panel is shown. Pass `defaultValue`, or `defaultValue={null}` to start with no selection.");
|
|
47
|
+
const context = {
|
|
48
|
+
rootId,
|
|
49
|
+
committed,
|
|
50
|
+
optimistic,
|
|
51
|
+
pending,
|
|
52
|
+
orientation,
|
|
53
|
+
activationMode,
|
|
54
|
+
loop,
|
|
55
|
+
disabled,
|
|
56
|
+
select: (next, event, reason) => {
|
|
57
|
+
if (next === optimistic) return;
|
|
58
|
+
dispatch(next, createActionDetails(event, { reason }));
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const element = useRender({
|
|
62
|
+
defaultElement: "div",
|
|
63
|
+
render,
|
|
64
|
+
state: {
|
|
65
|
+
value: optimistic,
|
|
66
|
+
pending,
|
|
67
|
+
orientation,
|
|
68
|
+
disabled
|
|
69
|
+
},
|
|
70
|
+
className,
|
|
71
|
+
style,
|
|
72
|
+
children,
|
|
73
|
+
props: rest,
|
|
74
|
+
libraryProps: {
|
|
75
|
+
"data-pending": dataAttr(pending),
|
|
76
|
+
"data-orientation": orientation,
|
|
77
|
+
"data-disabled": dataAttr(disabled)
|
|
78
|
+
},
|
|
79
|
+
ref: rootRef
|
|
80
|
+
});
|
|
81
|
+
return /* @__PURE__ */ jsx(TabsContext, {
|
|
82
|
+
value: context,
|
|
83
|
+
children: element
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
TabsRoot.displayName = "TabsRoot";
|
|
87
|
+
//#endregion
|
|
88
|
+
export { TabsRoot };
|
|
89
|
+
|
|
90
|
+
//# sourceMappingURL=tabs-root.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tabs-root.mjs","names":[],"sources":["../../src/tabs/tabs-root.tsx"],"sourcesContent":["\"use client\";\nimport { useId, useRef } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { createActionDetails } from \"../internal/action-details.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport { transitionTypes } from \"../internal/transition-types.js\";\nimport type { ActionDetails, ChangeAction, RenderProp, StateValue } from \"../internal/types.js\";\nimport { useControllableAction } from \"../internal/use-controllable-action.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { TabsContext } from \"./tabs-context.js\";\nimport type {\n TabsActivationMode,\n TabsChangeReason,\n TabsContextValue,\n TabsOrientation,\n} from \"./tabs-context.js\";\nimport { tabId } from \"./tabs-ids.js\";\n\nexport type { TabsActivationMode, TabsOrientation } from \"./tabs-context.js\";\n\n/** Details passed as the second argument of `valueChangeAction`. */\nexport interface TabsValueChangeDetails extends ActionDetails {\n /**\n * `\"activate\"`: the user selected the tab (click, Enter/Space, or focus in automatic mode).\n * `\"platform\"`: the browser revealed a `keepMounted=\"until-found\"` panel through find-in-page or a\n * text fragment (`details.event` is the `beforematch` event).\n */\n readonly reason: TabsChangeReason;\n}\n\nexport interface TabsRootState {\n /** Optimistic selected value, or `null`. Content rendered from it must not suspend. */\n readonly value: string | null;\n /** An action dispatched through this Tabs is in flight. */\n readonly pending: boolean;\n readonly orientation: TabsOrientation;\n readonly disabled: boolean;\n}\n\nexport interface TabsRootProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n \"className\" | \"style\" | \"children\" | \"defaultValue\"\n> {\n /** Controlled selected value. `null` selects nothing; `undefined` means uncontrolled. */\n value?: string | null;\n /** Initial value in uncontrolled mode. Defaults to `null` (no panel shown). */\n defaultValue?: string | null;\n /**\n * Runs inside the Root's transition whenever the user selects a tab. May be sync or async;\n * rejections reach the nearest Error Boundary and roll the selection back.\n *\n * With `activationMode=\"automatic\"` every arrow key press calls it; prefer `\"manual\"` when the\n * action awaits a server round trip. A `setState` placed after your own `await` is not a\n * Transition update; wrap it in `startTransition` (React limitation).\n */\n valueChangeAction?: ChangeAction<string, TabsValueChangeDetails>;\n /** Layout and arrow-key axis. Default `\"horizontal\"`. */\n orientation?: TabsOrientation;\n /**\n * `\"automatic\"`: selection follows focus. `\"manual\"`: Enter, Space or click selects.\n * Default `\"automatic\"`.\n */\n activationMode?: TabsActivationMode;\n /** Whether arrow keys wrap from the last tab to the first. Default `true`. */\n loop?: boolean;\n /** Disables every Tab (focusable, never dispatches). */\n disabled?: boolean;\n className?: StateValue<string | undefined, TabsRootState>;\n style?: StateValue<CSSProperties | undefined, TabsRootState>;\n children?: StateValue<ReactNode, TabsRootState>;\n render?: RenderProp<ComponentPropsWithRef<\"div\">, TabsRootState>;\n}\n\n/** Looks up an element by id in the tree that contains `node` (document or shadow root). */\nfunction findById(node: Node | null, id: string): Element | null {\n const root = node?.getRootNode() ?? (typeof document === \"undefined\" ? null : document);\n if (root !== null && \"getElementById\" in root) {\n return (root as Document | ShadowRoot).getElementById(id);\n }\n return null;\n}\n\n/**\n * Owns the Tabs state (`value`), the transition, the ids and the context. Renders a `div` with\n * `data-pending` while a selection is in flight (including a suspending panel's wait).\n *\n * Owned keys (conventions 1.2): `data-pending`, `data-orientation`, `data-disabled`.\n */\nexport function TabsRoot({\n value,\n defaultValue,\n valueChangeAction,\n orientation = \"horizontal\",\n activationMode = \"automatic\",\n loop = true,\n disabled = false,\n className,\n style,\n children,\n render,\n ...rest\n}: TabsRootProps) {\n const rootId = useId();\n const rootRef = useRef<HTMLElement | null>(null);\n\n const { committed, optimistic, pending, dispatch } = useControllableAction<\n string | null,\n TabsValueChangeDetails\n >({\n controlled: value,\n defaultValue,\n initialValue: null,\n // Only ever dispatched with a string: the Tabs UI cannot deselect.\n action:\n valueChangeAction === undefined\n ? undefined\n : (next, details) => valueChangeAction(next as string, details),\n transitionTypes: (next, prev) => {\n // Direction is relative to the committed tab (design 10.2), by DOM order of the two tabs.\n if (next === null || prev === null || next === prev) return transitionTypes(\"tabs\");\n const prevTab = findById(rootRef.current, tabId(rootId, prev));\n const nextTab = findById(rootRef.current, tabId(rootId, next));\n if (prevTab === null || nextTab === null) return transitionTypes(\"tabs\");\n const following = prevTab.compareDocumentPosition(nextTab) & Node.DOCUMENT_POSITION_FOLLOWING;\n return transitionTypes(\"tabs\", following ? \"next\" : \"prev\");\n },\n warn: { component: \"Tabs.Root\", prop: \"value\" },\n });\n // `null` is a legitimate default (no panel), but forgetting both props is the likelier cause\n // of an empty Tabs (design open question 2, product owner decision).\n useDevWarning(\n value === undefined && defaultValue === undefined,\n \"Tabs.Root: neither `value` nor `defaultValue` was passed, so no tab is selected and no \" +\n \"panel is shown. Pass `defaultValue`, or `defaultValue={null}` to start with no selection.\",\n );\n\n const context: TabsContextValue = {\n rootId,\n committed,\n optimistic,\n pending,\n orientation,\n activationMode,\n loop,\n disabled,\n select: (next, event, reason) => {\n // No-op rule against the optimistic value (conventions 2.3 item 7, design section 9.4).\n if (next === optimistic) return;\n dispatch(next, createActionDetails(event, { reason }));\n },\n };\n\n const state: TabsRootState = { value: optimistic, pending, orientation, disabled };\n\n const element = useRender<ComponentPropsWithRef<\"div\">, TabsRootState>({\n defaultElement: \"div\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps: {\n \"data-pending\": dataAttr(pending),\n \"data-orientation\": orientation,\n \"data-disabled\": dataAttr(disabled),\n },\n ref: rootRef,\n });\n\n return <TabsContext value={context}>{element}</TabsContext>;\n}\n\nTabsRoot.displayName = \"TabsRoot\";\n"],"mappings":";;;;;;;;;;;;;AA2EA,SAAS,SAAS,MAAmB,IAA4B;CAC/D,MAAM,OAAO,MAAM,YAAY,MAAM,OAAO,aAAa,cAAc,OAAO;CAC9E,IAAI,SAAS,QAAQ,oBAAoB,MACvC,OAAQ,KAA+B,eAAe,EAAE;CAE1D,OAAO;AACT;;;;;;;AAQA,SAAgB,SAAS,EACvB,OACA,cACA,mBACA,cAAc,cACd,iBAAiB,aACjB,OAAO,MACP,WAAW,OACX,WACA,OACA,UACA,QACA,GAAG,QACa;CAChB,MAAM,SAAS,MAAM;CACrB,MAAM,UAAU,OAA2B,IAAI;CAE/C,MAAM,EAAE,WAAW,YAAY,SAAS,aAAa,sBAGnD;EACA,YAAY;EACZ;EACA,cAAc;EAEd,QACE,sBAAsB,KAAA,IAClB,KAAA,KACC,MAAM,YAAY,kBAAkB,MAAgB,OAAO;EAClE,kBAAkB,MAAM,SAAS;GAE/B,IAAI,SAAS,QAAQ,SAAS,QAAQ,SAAS,MAAM,OAAO,gBAAgB,MAAM;GAClF,MAAM,UAAU,SAAS,QAAQ,SAAS,MAAM,QAAQ,IAAI,CAAC;GAC7D,MAAM,UAAU,SAAS,QAAQ,SAAS,MAAM,QAAQ,IAAI,CAAC;GAC7D,IAAI,YAAY,QAAQ,YAAY,MAAM,OAAO,gBAAgB,MAAM;GACvE,MAAM,YAAY,QAAQ,wBAAwB,OAAO,IAAI,KAAK;GAClE,OAAO,gBAAgB,QAAQ,YAAY,SAAS,MAAM;EAC5D;EACA,MAAM;GAAE,WAAW;GAAa,MAAM;EAAQ;CAChD,CAAC;CAGD,cACE,UAAU,KAAA,KAAa,iBAAiB,KAAA,GACxC,kLAEF;CAEA,MAAM,UAA4B;EAChC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,SAAS,MAAM,OAAO,WAAW;GAE/B,IAAI,SAAS,YAAY;GACzB,SAAS,MAAM,oBAAoB,OAAO,EAAE,OAAO,CAAC,CAAC;EACvD;CACF;CAIA,MAAM,UAAU,UAAuD;EACrE,gBAAgB;EAChB;EACA,OAAA;GAL6B,OAAO;GAAY;GAAS;GAAa;EAKlE;EACJ;EACA;EACA;EACA,OAAO;EACP,cAAc;GACZ,gBAAgB,SAAS,OAAO;GAChC,oBAAoB;GACpB,iBAAiB,SAAS,QAAQ;EACpC;EACA,KAAK;CACP,CAAC;CAED,OAAO,oBAAC,aAAD;EAAa,OAAO;EAAU,UAAA;CAAqB,CAAA;AAC5D;AAEA,SAAS,cAAc"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { RenderProp, StateValue } from "../internal/types.mjs";
|
|
2
|
+
import { TabsOrientation } from "./tabs-context.mjs";
|
|
3
|
+
import { CSSProperties, ComponentPropsWithRef, ReactNode, ViewTransitionClass } from "react";
|
|
4
|
+
//#region src/tabs/tabs-tab.d.ts
|
|
5
|
+
export interface TabsTabState {
|
|
6
|
+
readonly value: string;
|
|
7
|
+
/** Optimistic: true as soon as the user selects this tab, before the action settles. */
|
|
8
|
+
readonly selected: boolean;
|
|
9
|
+
/** The Root is pending and this tab is the optimistic target. */
|
|
10
|
+
readonly pending: boolean;
|
|
11
|
+
readonly disabled: boolean;
|
|
12
|
+
readonly orientation: TabsOrientation;
|
|
13
|
+
}
|
|
14
|
+
export interface TabsTabProps extends Omit<ComponentPropsWithRef<"button">, "onClick" | "onKeyDown" | "onFocus" | "id" | "value" | "className" | "style" | "children"> {
|
|
15
|
+
/** The value this tab selects. Must be unique within the Root. */
|
|
16
|
+
value: string;
|
|
17
|
+
/** Focusable but inert: `aria-disabled="true"`, never dispatches. */
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Forwarded to the Tab's `<ViewTransition default>`. Default: no boundary. Fires only on the commit
|
|
21
|
+
* that settles the action (or rolls it back), never on the optimistic selection; animate the
|
|
22
|
+
* selection itself with a CSS `transition` on `[data-selected]`.
|
|
23
|
+
*
|
|
24
|
+
* Static: without `transition` the part renders no `<ViewTransition>`; changing it between
|
|
25
|
+
* `undefined` and a value remounts the element. Any value, `"none"` included, makes a mount or
|
|
26
|
+
* `<Activity>` reveal of this part inside a transition start a document view transition: the
|
|
27
|
+
* page cross-fades for about 250 ms and the next transition commit waits for it (issue #35).
|
|
28
|
+
*/
|
|
29
|
+
transition?: ViewTransitionClass;
|
|
30
|
+
className?: StateValue<string | undefined, TabsTabState>;
|
|
31
|
+
style?: StateValue<CSSProperties | undefined, TabsTabState>;
|
|
32
|
+
/** Rendered from optimistic state; must not suspend. */
|
|
33
|
+
children?: StateValue<ReactNode, TabsTabState>;
|
|
34
|
+
render?: RenderProp<ComponentPropsWithRef<"button">, TabsTabState>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A `button[role=tab]`. Renders the optimistic selection (`aria-selected`, `data-selected`,
|
|
38
|
+
* roving `tabindex`) and carries `anchor-name` for the Indicator.
|
|
39
|
+
*
|
|
40
|
+
* Owned keys (conventions 1.2): `role`, `id`, `aria-selected`, `aria-controls` (token-merged),
|
|
41
|
+
* `aria-disabled`, `tabIndex`, `focusgroupstart` (with `experimental_focusgroup`), `data-selected`, `data-pending`, `data-disabled`,
|
|
42
|
+
* `data-orientation`, `onClick`, `onKeyDown`, `onFocus`.
|
|
43
|
+
*/
|
|
44
|
+
export declare function TabsTab({ value, disabled: disabledProp, transition, className, style, children, render, ...rest }: TabsTabProps): ReactNode;
|
|
45
|
+
export declare namespace TabsTab {
|
|
46
|
+
var displayName: string;
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
//# sourceMappingURL=tabs-tab.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tabs-tab.d.mts","names":[],"sources":["../../src/tabs/tabs-tab.tsx"],"mappings":";;;;iBAmBiB;WACN;;WAEA;;WAEA;WACA;WACA,aAAa;;iBAGP,qBAAqB,KACpC;;EAIA;;EAEA;;;;;;;;;;;EAWA,aAAa;EACb,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;;EAE9C,WAAW,WAAW,WAAW;EACjC,SAAS,WAAW,iCAAiC;;;;;;;;;;wBA2BvC,UACd,OACA,UAAU,cACV,YACA,WACA,OACA,UACA,WACG,QACF,eAAY;yBATC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useTransitionBoundary } from "../internal/transition-boundary.mjs";
|
|
3
|
+
import { dataAttr } from "../internal/data-attr.mjs";
|
|
4
|
+
import { rendersButton, resolveStateValue, useRender } from "../internal/use-render.mjs";
|
|
5
|
+
import { TabsListContext, useTabsContext } from "./tabs-context.mjs";
|
|
6
|
+
import { anchorName, panelId, tabId } from "./tabs-ids.mjs";
|
|
7
|
+
import { use } from "react";
|
|
8
|
+
//#region src/tabs/tabs-tab.tsx
|
|
9
|
+
function isActivationKey(event) {
|
|
10
|
+
return (event.key === "Enter" || event.key === " ") && !event.altKey && !event.ctrlKey && !event.metaKey;
|
|
11
|
+
}
|
|
12
|
+
/** Focus came from another element inside the tab's own tablist (not from the list itself). */
|
|
13
|
+
function movedWithinList(event) {
|
|
14
|
+
const list = event.currentTarget.closest("[role=\"tablist\"]");
|
|
15
|
+
const from = event.relatedTarget;
|
|
16
|
+
return list !== null && from instanceof Node && from !== list && list.contains(from);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* A `button[role=tab]`. Renders the optimistic selection (`aria-selected`, `data-selected`,
|
|
20
|
+
* roving `tabindex`) and carries `anchor-name` for the Indicator.
|
|
21
|
+
*
|
|
22
|
+
* Owned keys (conventions 1.2): `role`, `id`, `aria-selected`, `aria-controls` (token-merged),
|
|
23
|
+
* `aria-disabled`, `tabIndex`, `focusgroupstart` (with `experimental_focusgroup`), `data-selected`, `data-pending`, `data-disabled`,
|
|
24
|
+
* `data-orientation`, `onClick`, `onKeyDown`, `onFocus`.
|
|
25
|
+
*/
|
|
26
|
+
function TabsTab({ value, disabled: disabledProp = false, transition, className, style, children, render, ...rest }) {
|
|
27
|
+
const ctx = useTabsContext("Tabs.Tab");
|
|
28
|
+
const listCtx = use(TabsListContext);
|
|
29
|
+
const selected = ctx.optimistic === value;
|
|
30
|
+
const pending = ctx.pending && selected;
|
|
31
|
+
const disabled = ctx.disabled || disabledProp;
|
|
32
|
+
const anchor = anchorName(ctx.rootId, value);
|
|
33
|
+
const state = {
|
|
34
|
+
value,
|
|
35
|
+
selected,
|
|
36
|
+
pending,
|
|
37
|
+
disabled,
|
|
38
|
+
orientation: ctx.orientation
|
|
39
|
+
};
|
|
40
|
+
const element = useRender({
|
|
41
|
+
defaultElement: "button",
|
|
42
|
+
render,
|
|
43
|
+
state,
|
|
44
|
+
className,
|
|
45
|
+
style: (s) => ({
|
|
46
|
+
anchorName: anchor,
|
|
47
|
+
...resolveStateValue(style, s)
|
|
48
|
+
}),
|
|
49
|
+
children,
|
|
50
|
+
props: rendersButton(render) ? {
|
|
51
|
+
...rest,
|
|
52
|
+
type: "button"
|
|
53
|
+
} : rest,
|
|
54
|
+
libraryProps: {
|
|
55
|
+
role: "tab",
|
|
56
|
+
id: tabId(ctx.rootId, value),
|
|
57
|
+
"aria-selected": selected,
|
|
58
|
+
"aria-controls": panelId(ctx.rootId, value),
|
|
59
|
+
"aria-disabled": disabled ? true : void 0,
|
|
60
|
+
tabIndex: selected ? 0 : listCtx?.nativeFocusgroup ? void 0 : -1,
|
|
61
|
+
...listCtx?.focusgroup === true && { focusgroupstart: selected ? "" : void 0 },
|
|
62
|
+
"data-selected": dataAttr(selected),
|
|
63
|
+
"data-pending": dataAttr(pending),
|
|
64
|
+
"data-disabled": dataAttr(disabled),
|
|
65
|
+
"data-orientation": ctx.orientation,
|
|
66
|
+
onClick: (event) => {
|
|
67
|
+
if (disabled) return;
|
|
68
|
+
ctx.select(value, event.nativeEvent, "activate");
|
|
69
|
+
},
|
|
70
|
+
onKeyDown: (event) => {
|
|
71
|
+
if (!isActivationKey(event)) return;
|
|
72
|
+
event.preventDefault();
|
|
73
|
+
if (disabled) return;
|
|
74
|
+
ctx.select(value, event.nativeEvent, "activate");
|
|
75
|
+
},
|
|
76
|
+
onFocus: (event) => {
|
|
77
|
+
if (ctx.activationMode !== "automatic" || disabled || selected) return;
|
|
78
|
+
if (!movedWithinList(event)) return;
|
|
79
|
+
ctx.select(value, event.nativeEvent, "activate");
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
return useTransitionBoundary("Tabs.Tab", transition, element);
|
|
84
|
+
}
|
|
85
|
+
TabsTab.displayName = "TabsTab";
|
|
86
|
+
//#endregion
|
|
87
|
+
export { TabsTab };
|
|
88
|
+
|
|
89
|
+
//# sourceMappingURL=tabs-tab.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tabs-tab.mjs","names":[],"sources":["../../src/tabs/tabs-tab.tsx"],"sourcesContent":["\"use client\";\nimport { use } from \"react\";\nimport type {\n ComponentPropsWithRef,\n CSSProperties,\n FocusEvent,\n KeyboardEvent,\n MouseEvent,\n ReactNode,\n ViewTransitionClass,\n} from \"react\";\nimport { useTransitionBoundary } from \"../internal/transition-boundary.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { rendersButton, resolveStateValue, useRender } from \"../internal/use-render.js\";\nimport { TabsListContext, useTabsContext } from \"./tabs-context.js\";\nimport type { TabsOrientation } from \"./tabs-context.js\";\nimport { anchorName, panelId, tabId } from \"./tabs-ids.js\";\n\nexport interface TabsTabState {\n readonly value: string;\n /** Optimistic: true as soon as the user selects this tab, before the action settles. */\n readonly selected: boolean;\n /** The Root is pending and this tab is the optimistic target. */\n readonly pending: boolean;\n readonly disabled: boolean;\n readonly orientation: TabsOrientation;\n}\n\nexport interface TabsTabProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n \"onClick\" | \"onKeyDown\" | \"onFocus\" | \"id\" | \"value\" | \"className\" | \"style\" | \"children\"\n> {\n /** The value this tab selects. Must be unique within the Root. */\n value: string;\n /** Focusable but inert: `aria-disabled=\"true\"`, never dispatches. */\n disabled?: boolean;\n /**\n * Forwarded to the Tab's `<ViewTransition default>`. Default: no boundary. Fires only on the commit\n * that settles the action (or rolls it back), never on the optimistic selection; animate the\n * selection itself with a CSS `transition` on `[data-selected]`.\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, TabsTabState>;\n style?: StateValue<CSSProperties | undefined, TabsTabState>;\n /** Rendered from optimistic state; must not suspend. */\n children?: StateValue<ReactNode, TabsTabState>;\n render?: RenderProp<ComponentPropsWithRef<\"button\">, TabsTabState>;\n}\n\nfunction isActivationKey(event: KeyboardEvent): boolean {\n return (\n (event.key === \"Enter\" || event.key === \" \") &&\n !event.altKey &&\n !event.ctrlKey &&\n !event.metaKey\n );\n}\n\n/** Focus came from another element inside the tab's own tablist (not from the list itself). */\nfunction movedWithinList(event: FocusEvent<HTMLElement>): boolean {\n const list = event.currentTarget.closest('[role=\"tablist\"]');\n const from = event.relatedTarget;\n return list !== null && from instanceof Node && from !== list && list.contains(from);\n}\n\n/**\n * A `button[role=tab]`. Renders the optimistic selection (`aria-selected`, `data-selected`,\n * roving `tabindex`) and carries `anchor-name` for the Indicator.\n *\n * Owned keys (conventions 1.2): `role`, `id`, `aria-selected`, `aria-controls` (token-merged),\n * `aria-disabled`, `tabIndex`, `focusgroupstart` (with `experimental_focusgroup`), `data-selected`, `data-pending`, `data-disabled`,\n * `data-orientation`, `onClick`, `onKeyDown`, `onFocus`.\n */\nexport function TabsTab({\n value,\n disabled: disabledProp = false,\n transition,\n className,\n style,\n children,\n render,\n ...rest\n}: TabsTabProps) {\n const ctx = useTabsContext(\"Tabs.Tab\");\n const listCtx = use(TabsListContext);\n const selected = ctx.optimistic === value;\n const pending = ctx.pending && selected;\n const disabled = ctx.disabled || disabledProp;\n const anchor = anchorName(ctx.rootId, value);\n\n const state: TabsTabState = {\n value,\n selected,\n pending,\n disabled,\n orientation: ctx.orientation,\n };\n\n const element = useRender<ComponentPropsWithRef<\"button\">, TabsTabState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n // `anchor-name` sits under the consumer's style: the consumer wins (design 13.5).\n style: (s) => ({ anchorName: anchor, ...resolveStateValue(style, s) }),\n children,\n // `type=\"button\"` only on a real button (no form submission); an `<a>` must not get it.\n props: rendersButton(render) ? { ...rest, type: \"button\" } : rest,\n libraryProps: {\n role: \"tab\",\n id: tabId(ctx.rootId, value),\n \"aria-selected\": selected,\n \"aria-controls\": panelId(ctx.rootId, value),\n \"aria-disabled\": disabled ? true : undefined,\n // Roving tabindex, except on the native `focusgroup` path, where the browser owns the tab\n // stop and a negative tabindex would take the Tab out of the group (TabsListContextValue).\n tabIndex: selected ? 0 : listCtx?.nativeFocusgroup ? undefined : -1,\n // Always rendered with the flag (inert where unsupported), so markup is stable.\n ...(listCtx?.focusgroup === true && { focusgroupstart: selected ? \"\" : undefined }),\n \"data-selected\": dataAttr(selected),\n \"data-pending\": dataAttr(pending),\n \"data-disabled\": dataAttr(disabled),\n \"data-orientation\": ctx.orientation,\n onClick: (event: MouseEvent<HTMLButtonElement>) => {\n if (disabled) return;\n ctx.select(value, event.nativeEvent, \"activate\");\n },\n // Enter and Space are handled on keydown so a `render`ed non-button element (a link)\n // behaves the same; `preventDefault` also stops the native click / navigation.\n onKeyDown: (event: KeyboardEvent<HTMLButtonElement>) => {\n if (!isActivationKey(event)) return;\n event.preventDefault();\n if (disabled) return;\n ctx.select(value, event.nativeEvent, \"activate\");\n },\n // Automatic activation is selection-follows-focus (design 4.1), but only for focus that\n // moves between tabs inside this list (the arrow-key path, the native `focusgroup` path,\n // programmatic movement). Focus entering from outside never selects: the Tab key, window\n // re-activation, focus restore, `autoFocus`, and the List's own redirect when no tab is the\n // tab stop. A click still selects through `onClick`.\n onFocus: (event: FocusEvent<HTMLButtonElement>) => {\n if (ctx.activationMode !== \"automatic\" || disabled || selected) return;\n if (!movedWithinList(event)) return;\n ctx.select(value, event.nativeEvent, \"activate\");\n },\n },\n });\n\n return useTransitionBoundary(\"Tabs.Tab\", transition, element);\n}\n\nTabsTab.displayName = \"TabsTab\";\n"],"mappings":";;;;;;;;AAuDA,SAAS,gBAAgB,OAA+B;CACtD,QACG,MAAM,QAAQ,WAAW,MAAM,QAAQ,QACxC,CAAC,MAAM,UACP,CAAC,MAAM,WACP,CAAC,MAAM;AAEX;;AAGA,SAAS,gBAAgB,OAAyC;CAChE,MAAM,OAAO,MAAM,cAAc,QAAQ,oBAAkB;CAC3D,MAAM,OAAO,MAAM;CACnB,OAAO,SAAS,QAAQ,gBAAgB,QAAQ,SAAS,QAAQ,KAAK,SAAS,IAAI;AACrF;;;;;;;;;AAUA,SAAgB,QAAQ,EACtB,OACA,UAAU,eAAe,OACzB,YACA,WACA,OACA,UACA,QACA,GAAG,QACY;CACf,MAAM,MAAM,eAAe,UAAU;CACrC,MAAM,UAAU,IAAI,eAAe;CACnC,MAAM,WAAW,IAAI,eAAe;CACpC,MAAM,UAAU,IAAI,WAAW;CAC/B,MAAM,WAAW,IAAI,YAAY;CACjC,MAAM,SAAS,WAAW,IAAI,QAAQ,KAAK;CAE3C,MAAM,QAAsB;EAC1B;EACA;EACA;EACA;EACA,aAAa,IAAI;CACnB;CAEA,MAAM,UAAU,UAAyD;EACvE,gBAAgB;EAChB;EACA;EACA;EAEA,QAAQ,OAAO;GAAE,YAAY;GAAQ,GAAG,kBAAkB,OAAO,CAAC;EAAE;EACpE;EAEA,OAAO,cAAc,MAAM,IAAI;GAAE,GAAG;GAAM,MAAM;EAAS,IAAI;EAC7D,cAAc;GACZ,MAAM;GACN,IAAI,MAAM,IAAI,QAAQ,KAAK;GAC3B,iBAAiB;GACjB,iBAAiB,QAAQ,IAAI,QAAQ,KAAK;GAC1C,iBAAiB,WAAW,OAAO,KAAA;GAGnC,UAAU,WAAW,IAAI,SAAS,mBAAmB,KAAA,IAAY;GAEjE,GAAI,SAAS,eAAe,QAAQ,EAAE,iBAAiB,WAAW,KAAK,KAAA,EAAU;GACjF,iBAAiB,SAAS,QAAQ;GAClC,gBAAgB,SAAS,OAAO;GAChC,iBAAiB,SAAS,QAAQ;GAClC,oBAAoB,IAAI;GACxB,UAAU,UAAyC;IACjD,IAAI,UAAU;IACd,IAAI,OAAO,OAAO,MAAM,aAAa,UAAU;GACjD;GAGA,YAAY,UAA4C;IACtD,IAAI,CAAC,gBAAgB,KAAK,GAAG;IAC7B,MAAM,eAAe;IACrB,IAAI,UAAU;IACd,IAAI,OAAO,OAAO,MAAM,aAAa,UAAU;GACjD;GAMA,UAAU,UAAyC;IACjD,IAAI,IAAI,mBAAmB,eAAe,YAAY,UAAU;IAChE,IAAI,CAAC,gBAAgB,KAAK,GAAG;IAC7B,IAAI,OAAO,OAAO,MAAM,aAAa,UAAU;GACjD;EACF;CACF,CAAC;CAED,OAAO,sBAAsB,YAAY,YAAY,OAAO;AAC9D;AAEA,QAAQ,cAAc"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Toggle } from "./toggle.mjs";
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { ActionDetails, ChangeAction, RenderProp, StateValue } from "../internal/types.mjs";
|
|
2
|
+
import { CSSProperties, ComponentPropsWithRef, ReactNode, ViewTransitionClass } from "react";
|
|
3
|
+
//#region src/toggle/toggle.d.ts
|
|
4
|
+
export interface ToggleState {
|
|
5
|
+
/** Optimistic pressed state: what the user sees, including an in-flight change. */
|
|
6
|
+
readonly pressed: boolean;
|
|
7
|
+
/** An action dispatched by this Toggle 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 `pressedChangeAction`. */
|
|
13
|
+
export interface TogglePressedChangeDetails extends ActionDetails {}
|
|
14
|
+
export interface ToggleProps extends Omit<ComponentPropsWithRef<"button">, "onClick" | "type" | "aria-pressed" | "className" | "style" | "children"> {
|
|
15
|
+
/** Controlled pressed state. `undefined` means uncontrolled. */
|
|
16
|
+
pressed?: boolean;
|
|
17
|
+
/** Initial pressed state in uncontrolled mode. Default `false`. */
|
|
18
|
+
defaultPressed?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Called with the next pressed state on every user-initiated change. Runs inside the Toggle's
|
|
21
|
+
* transition; may be sync or async. Rejections reach the nearest Error Boundary.
|
|
22
|
+
*
|
|
23
|
+
* A `setState` placed after your own `await` is not a Transition update; wrap it in
|
|
24
|
+
* `startTransition` (React limitation).
|
|
25
|
+
*/
|
|
26
|
+
pressedChangeAction?: ChangeAction<boolean, TogglePressedChangeDetails>;
|
|
27
|
+
/** Renders a native disabled button; never dispatches. Default `false`. */
|
|
28
|
+
disabled?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Forwarded as `default` to the Toggle's `<ViewTransition>`. Default: no boundary. Animates the
|
|
31
|
+
* commit that ends an action (and rollbacks), not the optimistic flip itself; animate the flip
|
|
32
|
+
* with a CSS `transition` on `[data-pressed]`.
|
|
33
|
+
*
|
|
34
|
+
* Static: without `transition` the part renders no `<ViewTransition>`; changing it between
|
|
35
|
+
* `undefined` and a value remounts the element. Any value, `"none"` included, makes a mount or
|
|
36
|
+
* `<Activity>` reveal of this part inside a transition start a document view transition: the
|
|
37
|
+
* page cross-fades for about 250 ms and the next transition commit waits for it (issue #35).
|
|
38
|
+
*/
|
|
39
|
+
transition?: ViewTransitionClass;
|
|
40
|
+
className?: StateValue<string | undefined, ToggleState>;
|
|
41
|
+
style?: StateValue<CSSProperties | undefined, ToggleState>;
|
|
42
|
+
/** Rendered from optimistic state inside the button; must not suspend. */
|
|
43
|
+
children?: StateValue<ReactNode, ToggleState>;
|
|
44
|
+
/** Replace the rendered element (element form) or take over rendering (function form). */
|
|
45
|
+
render?: RenderProp<ComponentPropsWithRef<"button">, ToggleState>;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A two-state button (WAI-ARIA "Button" pattern, toggle variant). Renders
|
|
49
|
+
* `<button type="button" aria-pressed>`; the pressed state flips optimistically and the element
|
|
50
|
+
* carries `data-pending` while `pressedChangeAction` is in flight.
|
|
51
|
+
*/
|
|
52
|
+
export declare function Toggle({ pressed, defaultPressed, pressedChangeAction, disabled, transition, className, style, children, render, ...rest }: ToggleProps): ReactNode;
|
|
53
|
+
export declare namespace Toggle {
|
|
54
|
+
var displayName: string;
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
//# sourceMappingURL=toggle.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toggle.d.mts","names":[],"sources":["../../src/toggle/toggle.tsx"],"mappings":";;;iBAgBiB;;WAEN;;WAEA;;WAEA;;;iBAIM,mCAAmC;iBAKnC,oBAAoB,KACnC;;EAIA;;EAEA;;;;;;;;EAQA,sBAAsB,sBAAsB;;EAE5C;;;;;;;;;;;EAWA,aAAa;EACb,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;;EAE9C,WAAW,WAAW,WAAW;;EAEjC,SAAS,WAAW,iCAAiC;;;;;;;wBAQvC,SACd,SACA,gBACA,qBACA,UACA,YACA,WACA,OACA,UACA,WACG,QACF,cAAW;yBAXE"}
|
|
@@ -0,0 +1,59 @@
|
|
|
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 { rendersButton, useRender } from "../internal/use-render.mjs";
|
|
8
|
+
//#region src/toggle/toggle.tsx
|
|
9
|
+
/**
|
|
10
|
+
* A two-state button (WAI-ARIA "Button" pattern, toggle variant). Renders
|
|
11
|
+
* `<button type="button" aria-pressed>`; the pressed state flips optimistically and the element
|
|
12
|
+
* carries `data-pending` while `pressedChangeAction` is in flight.
|
|
13
|
+
*/
|
|
14
|
+
function Toggle({ pressed, defaultPressed, pressedChangeAction, disabled = false, transition, className, style, children, render, ...rest }) {
|
|
15
|
+
const { optimistic, pending, dispatch } = useControllableAction({
|
|
16
|
+
controlled: pressed,
|
|
17
|
+
defaultValue: defaultPressed,
|
|
18
|
+
initialValue: false,
|
|
19
|
+
action: pressedChangeAction,
|
|
20
|
+
transitionTypes: (next) => transitionTypes("toggle", next ? "press" : "release"),
|
|
21
|
+
warn: {
|
|
22
|
+
component: "Toggle",
|
|
23
|
+
prop: "pressed"
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
const element = useRender({
|
|
27
|
+
defaultElement: "button",
|
|
28
|
+
render,
|
|
29
|
+
state: {
|
|
30
|
+
pressed: optimistic,
|
|
31
|
+
pending,
|
|
32
|
+
disabled
|
|
33
|
+
},
|
|
34
|
+
className,
|
|
35
|
+
style,
|
|
36
|
+
children,
|
|
37
|
+
props: rendersButton(render) ? {
|
|
38
|
+
...rest,
|
|
39
|
+
type: "button"
|
|
40
|
+
} : rest,
|
|
41
|
+
libraryProps: {
|
|
42
|
+
disabled,
|
|
43
|
+
"aria-pressed": optimistic,
|
|
44
|
+
"data-pressed": dataAttr(optimistic),
|
|
45
|
+
"data-pending": dataAttr(pending),
|
|
46
|
+
"data-disabled": dataAttr(disabled),
|
|
47
|
+
onClick: (event) => {
|
|
48
|
+
if (disabled) return;
|
|
49
|
+
dispatch(!optimistic, createActionDetails(event.nativeEvent, {}));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
return useTransitionBoundary("Toggle", transition, element);
|
|
54
|
+
}
|
|
55
|
+
Toggle.displayName = "Toggle";
|
|
56
|
+
//#endregion
|
|
57
|
+
export { Toggle };
|
|
58
|
+
|
|
59
|
+
//# sourceMappingURL=toggle.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toggle.mjs","names":[],"sources":["../../src/toggle/toggle.tsx"],"sourcesContent":["\"use client\";\nimport type {\n ComponentPropsWithRef,\n CSSProperties,\n MouseEvent,\n ReactNode,\n ViewTransitionClass,\n} 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 { ActionDetails, ChangeAction, RenderProp, StateValue } from \"../internal/types.js\";\nimport { useControllableAction } from \"../internal/use-controllable-action.js\";\nimport { rendersButton, useRender } from \"../internal/use-render.js\";\n\nexport interface ToggleState {\n /** Optimistic pressed state: what the user sees, including an in-flight change. */\n readonly pressed: boolean;\n /** An action dispatched by this Toggle 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 `pressedChangeAction`. */\nexport interface TogglePressedChangeDetails extends ActionDetails {\n // v0 adds nothing beyond `event`; the interface exists so fields can be added without a\n // breaking change (e.g. a `reason` if a ToggleGroup ever dispatches on an item's behalf).\n}\n\nexport interface ToggleProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n \"onClick\" | \"type\" | \"aria-pressed\" | \"className\" | \"style\" | \"children\"\n> {\n /** Controlled pressed state. `undefined` means uncontrolled. */\n pressed?: boolean;\n /** Initial pressed state in uncontrolled mode. Default `false`. */\n defaultPressed?: boolean;\n /**\n * Called with the next pressed state on every user-initiated change. Runs inside the Toggle's\n * transition; may be sync or async. Rejections reach the nearest Error Boundary.\n *\n * A `setState` placed after your own `await` is not a Transition update; wrap it in\n * `startTransition` (React limitation).\n */\n pressedChangeAction?: ChangeAction<boolean, TogglePressedChangeDetails>;\n /** Renders a native disabled button; never dispatches. Default `false`. */\n disabled?: boolean;\n /**\n * Forwarded as `default` to the Toggle's `<ViewTransition>`. Default: no boundary. Animates the\n * commit that ends an action (and rollbacks), not the optimistic flip itself; animate the flip\n * with a CSS `transition` on `[data-pressed]`.\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, ToggleState>;\n style?: StateValue<CSSProperties | undefined, ToggleState>;\n /** Rendered from optimistic state inside the button; must not suspend. */\n children?: StateValue<ReactNode, ToggleState>;\n /** Replace the rendered element (element form) or take over rendering (function form). */\n render?: RenderProp<ComponentPropsWithRef<\"button\">, ToggleState>;\n}\n\n/**\n * A two-state button (WAI-ARIA \"Button\" pattern, toggle variant). Renders\n * `<button type=\"button\" aria-pressed>`; the pressed state flips optimistically and the element\n * carries `data-pending` while `pressedChangeAction` is in flight.\n */\nexport function Toggle({\n pressed,\n defaultPressed,\n pressedChangeAction,\n disabled = false,\n transition,\n className,\n style,\n children,\n render,\n ...rest\n}: ToggleProps) {\n const { optimistic, pending, dispatch } = useControllableAction<\n boolean,\n TogglePressedChangeDetails\n >({\n controlled: pressed,\n defaultValue: defaultPressed,\n initialValue: false,\n action: pressedChangeAction,\n transitionTypes: (next) => transitionTypes(\"toggle\", next ? \"press\" : \"release\"),\n warn: { component: \"Toggle\", prop: \"pressed\" },\n });\n\n const state: ToggleState = { pressed: optimistic, pending, disabled };\n\n const element = useRender<ComponentPropsWithRef<\"button\">, ToggleState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n style,\n children,\n // Pass-through layer: a `render` element's own props win over these (its `type` included).\n // `type=\"button\"` only on a real button; an `<a>` or a component element does not get it.\n props: rendersButton(render) ? { ...rest, type: \"button\" } : rest,\n // Library-controlled layer: wins over a `render` element's props.\n libraryProps: {\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 button on its own.\n disabled,\n \"aria-pressed\": optimistic,\n \"data-pressed\": dataAttr(optimistic),\n \"data-pending\": dataAttr(pending),\n \"data-disabled\": dataAttr(disabled),\n onClick: (event: MouseEvent<HTMLButtonElement>) => {\n // Native buttons do not fire `click` when disabled; the guard covers `render` elements\n // that do not honour `disabled`.\n if (disabled) return;\n dispatch(!optimistic, createActionDetails(event.nativeEvent, {}));\n },\n },\n });\n\n return useTransitionBoundary(\"Toggle\", transition, element);\n}\n\nToggle.displayName = \"Toggle\";\n"],"mappings":";;;;;;;;;;;;;AAyEA,SAAgB,OAAO,EACrB,SACA,gBACA,qBACA,WAAW,OACX,YACA,WACA,OACA,UACA,QACA,GAAG,QACW;CACd,MAAM,EAAE,YAAY,SAAS,aAAa,sBAGxC;EACA,YAAY;EACZ,cAAc;EACd,cAAc;EACd,QAAQ;EACR,kBAAkB,SAAS,gBAAgB,UAAU,OAAO,UAAU,SAAS;EAC/E,MAAM;GAAE,WAAW;GAAU,MAAM;EAAU;CAC/C,CAAC;CAID,MAAM,UAAU,UAAwD;EACtE,gBAAgB;EAChB;EACA,OAAA;GAL2B,SAAS;GAAY;GAAS;EAKrD;EACJ;EACA;EACA;EAGA,OAAO,cAAc,MAAM,IAAI;GAAE,GAAG;GAAM,MAAM;EAAS,IAAI;EAE7D,cAAc;GAGZ;GACA,gBAAgB;GAChB,gBAAgB,SAAS,UAAU;GACnC,gBAAgB,SAAS,OAAO;GAChC,iBAAiB,SAAS,QAAQ;GAClC,UAAU,UAAyC;IAGjD,IAAI,UAAU;IACd,SAAS,CAAC,YAAY,oBAAoB,MAAM,aAAa,CAAC,CAAC,CAAC;GAClE;EACF;CACF,CAAC;CAED,OAAO,sBAAsB,UAAU,YAAY,OAAO;AAC5D;AAEA,OAAO,cAAc"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import "../internal/platform-attributes.mjs";
|
|
2
|
+
import { TooltipAlign, TooltipPopup, TooltipPopupProps, TooltipPopupState, TooltipSide } from "./tooltip-popup.mjs";
|
|
3
|
+
import { TooltipProvides } from "./tooltip-context.mjs";
|
|
4
|
+
import { TooltipRoot, TooltipRootProps, TooltipRootState } from "./tooltip-root.mjs";
|
|
5
|
+
import { TooltipTrigger, TooltipTriggerProps, TooltipTriggerState } from "./tooltip-trigger.mjs";
|
|
6
|
+
//#region src/tooltip/index.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* Tooltip (WAI-ARIA APG Tooltip pattern): `Root` (no DOM) owns the id and the interaction,
|
|
9
|
+
* `Trigger` (button) is hovered or focused, `Popup` (`div[popover] role="tooltip"`) lives in the
|
|
10
|
+
* top layer and is placed with CSS Anchor Positioning. Open state is browser-owned: no `open`
|
|
11
|
+
* prop, no action, style with `:popover-open`. Render the Popup immediately after the Trigger.
|
|
12
|
+
*/
|
|
13
|
+
export declare const Tooltip: {
|
|
14
|
+
readonly Root: typeof TooltipRoot;
|
|
15
|
+
readonly Trigger: typeof TooltipTrigger;
|
|
16
|
+
readonly Popup: typeof TooltipPopup;
|
|
17
|
+
};
|
|
18
|
+
//#endregion
|
|
19
|
+
export type { TooltipAlign, TooltipPopupProps, TooltipPopupState, TooltipProvides, TooltipRootProps, TooltipRootState, TooltipSide, TooltipTriggerProps, TooltipTriggerState };
|
|
20
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/tooltip/index.ts"],"mappings":";;;;;;;;;;;;qBAaa;WACX,aAAI;WACJ,gBAAO;WACP,cAAK"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { TooltipPopup } from "./tooltip-popup.mjs";
|
|
2
|
+
import { TooltipRoot } from "./tooltip-root.mjs";
|
|
3
|
+
import { TooltipTrigger } from "./tooltip-trigger.mjs";
|
|
4
|
+
//#region src/tooltip/index.ts
|
|
5
|
+
/**
|
|
6
|
+
* Tooltip (WAI-ARIA APG Tooltip pattern): `Root` (no DOM) owns the id and the interaction,
|
|
7
|
+
* `Trigger` (button) is hovered or focused, `Popup` (`div[popover] role="tooltip"`) lives in the
|
|
8
|
+
* top layer and is placed with CSS Anchor Positioning. Open state is browser-owned: no `open`
|
|
9
|
+
* prop, no action, style with `:popover-open`. Render the Popup immediately after the Trigger.
|
|
10
|
+
*/
|
|
11
|
+
const Tooltip = {
|
|
12
|
+
Root: TooltipRoot,
|
|
13
|
+
Trigger: TooltipTrigger,
|
|
14
|
+
Popup: TooltipPopup
|
|
15
|
+
};
|
|
16
|
+
//#endregion
|
|
17
|
+
export { Tooltip };
|
|
18
|
+
|
|
19
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/tooltip/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 { TooltipPopup } from \"./tooltip-popup.js\";\nimport { TooltipRoot } from \"./tooltip-root.js\";\nimport { TooltipTrigger } from \"./tooltip-trigger.js\";\n\n/**\n * Tooltip (WAI-ARIA APG Tooltip pattern): `Root` (no DOM) owns the id and the interaction,\n * `Trigger` (button) is hovered or focused, `Popup` (`div[popover] role=\"tooltip\"`) lives in the\n * top layer and is placed with CSS Anchor Positioning. Open state is browser-owned: no `open`\n * prop, no action, style with `:popover-open`. Render the Popup immediately after the Trigger.\n */\nexport const Tooltip = {\n Root: TooltipRoot,\n Trigger: TooltipTrigger,\n Popup: TooltipPopup,\n} as const;\n\nexport type { TooltipRootProps, TooltipRootState, TooltipProvides } from \"./tooltip-root.js\";\nexport type { TooltipTriggerProps, TooltipTriggerState } from \"./tooltip-trigger.js\";\nexport type {\n TooltipPopupProps,\n TooltipPopupState,\n TooltipSide,\n TooltipAlign,\n} from \"./tooltip-popup.js\";\n"],"mappings":";;;;;;;;;;AAaA,MAAa,UAAU;CACrB,MAAM;CACN,SAAS;CACT,OAAO;AACT"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tooltip-context.d.mts","names":[],"sources":["../../src/tooltip/tooltip-context.ts"],"mappings":";;YAIY"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { createContext, use } from "react";
|
|
3
|
+
//#region src/tooltip/tooltip-context.ts
|
|
4
|
+
const TooltipContext = createContext(null);
|
|
5
|
+
function useTooltipContext(part) {
|
|
6
|
+
const context = use(TooltipContext);
|
|
7
|
+
if (context === null) throw new Error(`${part} must be rendered inside a Tooltip.Root.`);
|
|
8
|
+
return context;
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
export { TooltipContext, useTooltipContext };
|
|
12
|
+
|
|
13
|
+
//# sourceMappingURL=tooltip-context.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tooltip-context.mjs","names":[],"sources":["../../src/tooltip/tooltip-context.ts"],"sourcesContent":["\"use client\";\nimport { createContext, use } from \"react\";\n\n/** Which accessibility relation the tooltip provides for its trigger (design section 5.1). */\nexport type TooltipProvides = \"description\" | \"name\";\n\nexport interface TooltipContextValue {\n /** Id of the Popup; the Trigger references it through `aria-describedby` / `aria-labelledby`. */\n readonly popupId: string;\n readonly disabled: boolean;\n /** Written onto the Trigger as `interest-delay-start` / `-end` on the native path. */\n readonly delay: number;\n readonly closeDelay: number;\n readonly provides: TooltipProvides;\n /** Trigger and Popup register their elements through callback refs (design 12.2). */\n readonly setTriggerElement: (element: HTMLElement | null) => void;\n readonly setPopupElement: (element: HTMLElement | null) => void;\n /**\n * The Trigger reports whether it renders `interestfor`, so the Root's interaction effect re-runs\n * (and re-detects the native path) when `experimental_interestFor` changes after mount.\n */\n readonly setInterestForRequested: (requested: boolean) => void;\n}\n\nexport const TooltipContext = createContext<TooltipContextValue | null>(null);\n\nexport function useTooltipContext(part: string): TooltipContextValue {\n const context = use(TooltipContext);\n if (context === null) {\n throw new Error(`${part} must be rendered inside a Tooltip.Root.`);\n }\n return context;\n}\n"],"mappings":";;;AAwBA,MAAa,iBAAiB,cAA0C,IAAI;AAE5E,SAAgB,kBAAkB,MAAmC;CACnE,MAAM,UAAU,IAAI,cAAc;CAClC,IAAI,YAAY,MACd,MAAM,IAAI,MAAM,GAAG,KAAK,yCAAyC;CAEnE,OAAO;AACT"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { RenderProp, StateValue } from "../internal/types.mjs";
|
|
2
|
+
import { CSSProperties, ComponentPropsWithRef, ReactNode } from "react";
|
|
3
|
+
//#region src/tooltip/tooltip-popup.d.ts
|
|
4
|
+
export type TooltipSide = "top" | "bottom" | "left" | "right";
|
|
5
|
+
export type TooltipAlign = "start" | "center" | "end";
|
|
6
|
+
export interface TooltipPopupState {
|
|
7
|
+
readonly disabled: boolean;
|
|
8
|
+
readonly side: TooltipSide | null;
|
|
9
|
+
readonly align: TooltipAlign;
|
|
10
|
+
}
|
|
11
|
+
export interface TooltipPopupProps extends Omit<ComponentPropsWithRef<"div">, "id" | "popover" | "role" | "className" | "style" | "children"> {
|
|
12
|
+
/**
|
|
13
|
+
* Placement relative to the trigger through inline `position-area`, with
|
|
14
|
+
* `position-try-fallbacks` flipping it when it does not fit. Default `"top"`. `null` emits no
|
|
15
|
+
* inline positioning (and no `data-side` / `data-align`), so a stylesheet owns placement.
|
|
16
|
+
*/
|
|
17
|
+
side?: TooltipSide | null;
|
|
18
|
+
/** Alignment along the side. Default `"center"`. Ignored when `side` is `null`. */
|
|
19
|
+
align?: TooltipAlign;
|
|
20
|
+
/**
|
|
21
|
+
* Experimental (Chrome 151+, Firefox 153+; Safari 27.0 treats it as `"manual"`). Renders
|
|
22
|
+
* `popover="hint"` instead of `popover="manual"`: native light dismiss, and the tooltip can
|
|
23
|
+
* open over an `auto` popover (a menu) without closing it. The JS Escape / pointerdown handling
|
|
24
|
+
* stays in place for engines that do not know `hint`. Default `false`.
|
|
25
|
+
*/
|
|
26
|
+
experimental_hint?: boolean;
|
|
27
|
+
className?: StateValue<string | undefined, TooltipPopupState>;
|
|
28
|
+
style?: StateValue<CSSProperties | undefined, TooltipPopupState>;
|
|
29
|
+
/**
|
|
30
|
+
* Rendered at mount (hidden by the popover's `display: none`), not at open time. Tooltip
|
|
31
|
+
* content should be static; if it must suspend, put the `<Suspense>` boundary inside the Popup.
|
|
32
|
+
*/
|
|
33
|
+
children?: StateValue<ReactNode, TooltipPopupState>;
|
|
34
|
+
render?: RenderProp<ComponentPropsWithRef<"div">, TooltipPopupState>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The tooltip content: a `div` with `popover` and `role="tooltip"`, shown in the top layer and
|
|
38
|
+
* anchored to the trigger implicitly (`interestfor` or `showPopover({ source })`). Open state is
|
|
39
|
+
* browser-owned: style it with `:popover-open` and animate it with `@starting-style` in CSS.
|
|
40
|
+
*
|
|
41
|
+
* Owned keys (conventions 1.2): `id`, `popover`, `role`, `data-disabled`, and `data-side` /
|
|
42
|
+
* `data-align` unless `side` is `null`.
|
|
43
|
+
*/
|
|
44
|
+
export declare function TooltipPopup({ side, align, experimental_hint, className, style, children, render, ...rest }: TooltipPopupProps): ReactNode;
|
|
45
|
+
export declare namespace TooltipPopup {
|
|
46
|
+
var displayName: string;
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
//# sourceMappingURL=tooltip-popup.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tooltip-popup.d.mts","names":[],"sources":["../../src/tooltip/tooltip-popup.tsx"],"mappings":";;;YASY;YACA;iBAEK;WACN;WACA,MAAM;WACN,OAAO;;iBAGD,0BAA0B,KACzC;;;;;;EAQA,OAAO;;EAEP,QAAQ;;;;;;;EAOR;EACA,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;;;;;EAK9C,WAAW,WAAW,WAAW;EACjC,SAAS,WAAW,8BAA8B;;;;;;;;;;wBAWpC,eACd,MACA,OACA,mBACA,WACA,OACA,UACA,WACG,QACF,oBAAiB;yBATJ"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { dataAttr } from "../internal/data-attr.mjs";
|
|
3
|
+
import { resolveStateValue, useRender } from "../internal/use-render.mjs";
|
|
4
|
+
import { POSITION_TRY_FALLBACKS, positionArea } from "../internal/anchor-position.mjs";
|
|
5
|
+
import { useTooltipContext } from "./tooltip-context.mjs";
|
|
6
|
+
//#region src/tooltip/tooltip-popup.tsx
|
|
7
|
+
/**
|
|
8
|
+
* The tooltip content: a `div` with `popover` and `role="tooltip"`, shown in the top layer and
|
|
9
|
+
* anchored to the trigger implicitly (`interestfor` or `showPopover({ source })`). Open state is
|
|
10
|
+
* browser-owned: style it with `:popover-open` and animate it with `@starting-style` in CSS.
|
|
11
|
+
*
|
|
12
|
+
* Owned keys (conventions 1.2): `id`, `popover`, `role`, `data-disabled`, and `data-side` /
|
|
13
|
+
* `data-align` unless `side` is `null`.
|
|
14
|
+
*/
|
|
15
|
+
function TooltipPopup({ side = "top", align = "center", experimental_hint = false, className, style, children, render, ...rest }) {
|
|
16
|
+
const ctx = useTooltipContext("Tooltip.Popup");
|
|
17
|
+
const state = {
|
|
18
|
+
disabled: ctx.disabled,
|
|
19
|
+
side,
|
|
20
|
+
align
|
|
21
|
+
};
|
|
22
|
+
const libraryProps = {
|
|
23
|
+
id: ctx.popupId,
|
|
24
|
+
popover: experimental_hint ? "hint" : "manual",
|
|
25
|
+
role: "tooltip",
|
|
26
|
+
"data-disabled": dataAttr(ctx.disabled),
|
|
27
|
+
...side !== null && {
|
|
28
|
+
"data-side": side,
|
|
29
|
+
"data-align": align
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
return useRender({
|
|
33
|
+
defaultElement: "div",
|
|
34
|
+
render,
|
|
35
|
+
state,
|
|
36
|
+
className,
|
|
37
|
+
style: side === null ? style : (s) => ({
|
|
38
|
+
positionArea: positionArea(side, align),
|
|
39
|
+
positionTryFallbacks: POSITION_TRY_FALLBACKS,
|
|
40
|
+
...resolveStateValue(style, s)
|
|
41
|
+
}),
|
|
42
|
+
children,
|
|
43
|
+
props: rest,
|
|
44
|
+
libraryProps,
|
|
45
|
+
ref: ctx.setPopupElement
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
TooltipPopup.displayName = "TooltipPopup";
|
|
49
|
+
//#endregion
|
|
50
|
+
export { TooltipPopup };
|
|
51
|
+
|
|
52
|
+
//# sourceMappingURL=tooltip-popup.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tooltip-popup.mjs","names":[],"sources":["../../src/tooltip/tooltip-popup.tsx"],"sourcesContent":["\"use client\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { POSITION_TRY_FALLBACKS, positionArea } from \"../internal/anchor-position.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { resolveStateValue, useRender } from \"../internal/use-render.js\";\nimport type { DataAttributes } from \"../internal/use-render.js\";\nimport { useTooltipContext } from \"./tooltip-context.js\";\n\nexport type TooltipSide = \"top\" | \"bottom\" | \"left\" | \"right\";\nexport type TooltipAlign = \"start\" | \"center\" | \"end\";\n\nexport interface TooltipPopupState {\n readonly disabled: boolean;\n readonly side: TooltipSide | null;\n readonly align: TooltipAlign;\n}\n\nexport interface TooltipPopupProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n \"id\" | \"popover\" | \"role\" | \"className\" | \"style\" | \"children\"\n> {\n /**\n * Placement relative to the trigger through inline `position-area`, with\n * `position-try-fallbacks` flipping it when it does not fit. Default `\"top\"`. `null` emits no\n * inline positioning (and no `data-side` / `data-align`), so a stylesheet owns placement.\n */\n side?: TooltipSide | null;\n /** Alignment along the side. Default `\"center\"`. Ignored when `side` is `null`. */\n align?: TooltipAlign;\n /**\n * Experimental (Chrome 151+, Firefox 153+; Safari 27.0 treats it as `\"manual\"`). Renders\n * `popover=\"hint\"` instead of `popover=\"manual\"`: native light dismiss, and the tooltip can\n * open over an `auto` popover (a menu) without closing it. The JS Escape / pointerdown handling\n * stays in place for engines that do not know `hint`. Default `false`.\n */\n experimental_hint?: boolean;\n className?: StateValue<string | undefined, TooltipPopupState>;\n style?: StateValue<CSSProperties | undefined, TooltipPopupState>;\n /**\n * Rendered at mount (hidden by the popover's `display: none`), not at open time. Tooltip\n * content should be static; if it must suspend, put the `<Suspense>` boundary inside the Popup.\n */\n children?: StateValue<ReactNode, TooltipPopupState>;\n render?: RenderProp<ComponentPropsWithRef<\"div\">, TooltipPopupState>;\n}\n\n/**\n * The tooltip content: a `div` with `popover` and `role=\"tooltip\"`, shown in the top layer and\n * anchored to the trigger implicitly (`interestfor` or `showPopover({ source })`). Open state is\n * browser-owned: style it with `:popover-open` and animate it with `@starting-style` in CSS.\n *\n * Owned keys (conventions 1.2): `id`, `popover`, `role`, `data-disabled`, and `data-side` /\n * `data-align` unless `side` is `null`.\n */\nexport function TooltipPopup({\n side = \"top\",\n align = \"center\",\n experimental_hint = false,\n className,\n style,\n children,\n render,\n ...rest\n}: TooltipPopupProps) {\n const ctx = useTooltipContext(\"Tooltip.Popup\");\n const state: TooltipPopupState = { disabled: ctx.disabled, side, align };\n\n const libraryProps: ComponentPropsWithRef<\"div\"> & DataAttributes = {\n id: ctx.popupId,\n popover: experimental_hint ? \"hint\" : \"manual\",\n role: \"tooltip\",\n \"data-disabled\": dataAttr(ctx.disabled),\n // Not owned with `side={null}`: the stylesheet (or a render element) may set them.\n ...(side !== null && { \"data-side\": side, \"data-align\": align }),\n };\n\n return useRender<ComponentPropsWithRef<\"div\">, TooltipPopupState>({\n defaultElement: \"div\",\n render,\n state,\n className,\n // The inline placement sits under the consumer's style: the consumer wins (design 3).\n style:\n side === null\n ? style\n : (s) => ({\n positionArea: positionArea(side, align),\n positionTryFallbacks: POSITION_TRY_FALLBACKS,\n ...resolveStateValue(style, s),\n }),\n children,\n props: rest,\n libraryProps,\n ref: ctx.setPopupElement,\n });\n}\n\nTooltipPopup.displayName = \"TooltipPopup\";\n"],"mappings":";;;;;;;;;;;;;;AAuDA,SAAgB,aAAa,EAC3B,OAAO,OACP,QAAQ,UACR,oBAAoB,OACpB,WACA,OACA,UACA,QACA,GAAG,QACiB;CACpB,MAAM,MAAM,kBAAkB,eAAe;CAC7C,MAAM,QAA2B;EAAE,UAAU,IAAI;EAAU;EAAM;CAAM;CAEvE,MAAM,eAA8D;EAClE,IAAI,IAAI;EACR,SAAS,oBAAoB,SAAS;EACtC,MAAM;EACN,iBAAiB,SAAS,IAAI,QAAQ;EAEtC,GAAI,SAAS,QAAQ;GAAE,aAAa;GAAM,cAAc;EAAM;CAChE;CAEA,OAAO,UAA2D;EAChE,gBAAgB;EAChB;EACA;EACA;EAEA,OACE,SAAS,OACL,SACC,OAAO;GACN,cAAc,aAAa,MAAM,KAAK;GACtC,sBAAsB;GACtB,GAAG,kBAAkB,OAAO,CAAC;EAC/B;EACN;EACA,OAAO;EACP;EACA,KAAK,IAAI;CACX,CAAC;AACH;AAEA,aAAa,cAAc"}
|