@funstack/ui 0.1.0-alpha.0 → 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/README.md +1 -1
- 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.map +1 -1
- package/dist/dialog/dialog-close.mjs +4 -4
- package/dist/dialog/dialog-close.mjs.map +1 -1
- package/dist/dialog/dialog-context.mjs.map +1 -1
- package/dist/dialog/dialog-popup.mjs +1 -0
- package/dist/dialog/dialog-popup.mjs.map +1 -1
- package/dist/dialog/dialog-root.mjs +9 -1
- package/dist/dialog/dialog-root.mjs.map +1 -1
- package/dist/index.d.mts +23 -1
- package/dist/index.mjs +4 -1
- 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/platform-attributes.d.mts +6 -0
- package/dist/internal/platform-attributes.d.mts.map +1 -1
- package/dist/internal/sync-presence.mjs +19 -0
- package/dist/internal/sync-presence.mjs.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-delayed-pair.mjs +40 -0
- package/dist/internal/use-delayed-pair.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-native-group-reconciliation.mjs +2 -2
- package/dist/internal/use-native-group-reconciliation.mjs.map +1 -1
- 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/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/radio-group-item.d.mts.map +1 -1
- package/dist/radio-group/radio-group-item.mjs +1 -6
- package/dist/radio-group/radio-group-item.mjs.map +1 -1
- package/dist/tabs/tabs-context.mjs +2 -1
- package/dist/tabs/tabs-context.mjs.map +1 -1
- package/dist/tabs/tabs-list.d.mts +1 -1
- package/dist/tabs/tabs-list.d.mts.map +1 -1
- package/dist/tabs/tabs-list.mjs +17 -9
- package/dist/tabs/tabs-list.mjs.map +1 -1
- package/dist/tabs/tabs-tab.d.mts +1 -1
- package/dist/tabs/tabs-tab.d.mts.map +1 -1
- package/dist/tabs/tabs-tab.mjs +6 -3
- package/dist/tabs/tabs-tab.mjs.map +1 -1
- package/dist/tooltip/tooltip-popup.d.mts.map +1 -1
- package/dist/tooltip/tooltip-popup.mjs +2 -7
- package/dist/tooltip/tooltip-popup.mjs.map +1 -1
- package/dist/tooltip/use-tooltip-interaction.mjs +5 -17
- package/dist/tooltip/use-tooltip-interaction.mjs.map +1 -1
- package/package.json +4 -1
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useRender } from "../internal/use-render.mjs";
|
|
3
|
+
import { usePopoverContext } from "./popover-context.mjs";
|
|
4
|
+
import { usePopoverTrigger } from "../internal/use-popover-trigger.mjs";
|
|
5
|
+
//#region src/popover/popover-trigger.tsx
|
|
6
|
+
/**
|
|
7
|
+
* The toggle: `<button type="button" commandfor command="toggle-popover" aria-expanded>`. In
|
|
8
|
+
* JS-free mode the browser runs the command and the Popup keeps `aria-expanded` current. In
|
|
9
|
+
* library mode the click is cancelled (so the command does not run) and dispatched through the
|
|
10
|
+
* Root's transition: an open, or a library-owned close while open. The button shows optimistic
|
|
11
|
+
* `data-open` / `aria-expanded` and `data-pending` until the change has landed.
|
|
12
|
+
*
|
|
13
|
+
* Owned keys (conventions 1.2): `type` (on a `<button>` only), `disabled`, `commandfor`,
|
|
14
|
+
* `command`, `aria-expanded`, `onClick` (library mode), `data-open`, `data-pending`,
|
|
15
|
+
* `data-disabled`. `aria-haspopup` and `aria-controls` pass through.
|
|
16
|
+
*/
|
|
17
|
+
function PopoverTrigger({ disabled = false, className, style, children, render, ...rest }) {
|
|
18
|
+
const ctx = usePopoverContext("Popover.Trigger");
|
|
19
|
+
const { state, libraryProps, ref } = usePopoverTrigger(ctx, {
|
|
20
|
+
part: "Popover.Trigger",
|
|
21
|
+
disabled,
|
|
22
|
+
render
|
|
23
|
+
});
|
|
24
|
+
return useRender({
|
|
25
|
+
defaultElement: "button",
|
|
26
|
+
render,
|
|
27
|
+
state,
|
|
28
|
+
className,
|
|
29
|
+
style,
|
|
30
|
+
children,
|
|
31
|
+
props: rest,
|
|
32
|
+
libraryProps,
|
|
33
|
+
ref
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
PopoverTrigger.displayName = "PopoverTrigger";
|
|
37
|
+
//#endregion
|
|
38
|
+
export { PopoverTrigger };
|
|
39
|
+
|
|
40
|
+
//# sourceMappingURL=popover-trigger.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"popover-trigger.mjs","names":[],"sources":["../../src/popover/popover-trigger.tsx"],"sourcesContent":["\"use client\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { usePopoverTrigger } from \"../internal/use-popover-trigger.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { usePopoverContext } from \"./popover-context.js\";\n\nexport interface PopoverTriggerState {\n /** Optimistic open state (library mode); `false` in JS-free mode. */\n readonly open: boolean;\n /** The Root's transition is pending (an open, a part-initiated close or a dismissal notification). */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\nexport interface PopoverTriggerProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n \"onClick\" | \"aria-expanded\" | \"className\" | \"style\" | \"children\"\n> {\n /** Native `disabled`: a disabled button is not a valid invoker and never dispatches. */\n disabled?: boolean;\n className?: StateValue<string | undefined, PopoverTriggerState>;\n style?: StateValue<CSSProperties | undefined, PopoverTriggerState>;\n /** Rendered from optimistic state; must not suspend. */\n children?: StateValue<ReactNode, PopoverTriggerState>;\n /** Should render a `<button>`; another element works only in library mode (click handler). */\n render?: RenderProp<ComponentPropsWithRef<\"button\">, PopoverTriggerState>;\n}\n\n/**\n * The toggle: `<button type=\"button\" commandfor command=\"toggle-popover\" aria-expanded>`. In\n * JS-free mode the browser runs the command and the Popup keeps `aria-expanded` current. In\n * library mode the click is cancelled (so the command does not run) and dispatched through the\n * Root's transition: an open, or a library-owned close while open. The button shows optimistic\n * `data-open` / `aria-expanded` and `data-pending` until the change has landed.\n *\n * Owned keys (conventions 1.2): `type` (on a `<button>` only), `disabled`, `commandfor`,\n * `command`, `aria-expanded`, `onClick` (library mode), `data-open`, `data-pending`,\n * `data-disabled`. `aria-haspopup` and `aria-controls` pass through.\n */\nexport function PopoverTrigger({\n disabled = false,\n className,\n style,\n children,\n render,\n ...rest\n}: PopoverTriggerProps) {\n const ctx = usePopoverContext(\"Popover.Trigger\");\n const { state, libraryProps, ref } = usePopoverTrigger(ctx, {\n part: \"Popover.Trigger\",\n disabled,\n render,\n });\n\n return useRender<ComponentPropsWithRef<\"button\">, PopoverTriggerState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps,\n ref,\n });\n}\n\nPopoverTrigger.displayName = \"PopoverTrigger\";\n"],"mappings":";;;;;;;;;;;;;;;;AAwCA,SAAgB,eAAe,EAC7B,WAAW,OACX,WACA,OACA,UACA,QACA,GAAG,QACmB;CACtB,MAAM,MAAM,kBAAkB,iBAAiB;CAC/C,MAAM,EAAE,OAAO,cAAc,QAAQ,kBAAkB,KAAK;EAC1D,MAAM;EACN;EACA;CACF,CAAC;CAED,OAAO,UAAgE;EACrE,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA,OAAO;EACP;EACA;CACF,CAAC;AACH;AAEA,eAAe,cAAc"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"radio-group-item.d.mts","names":[],"sources":["../../src/radio-group/radio-group-item.tsx"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"radio-group-item.d.mts","names":[],"sources":["../../src/radio-group/radio-group-item.tsx"],"mappings":";;;iBAUiB;WACN;;WAEA;;WAEA;;WAEA;;iBAGM,4BAA4B,KAC3C;;EAeA;;EAEA;;;;;;;;;;;EAWA,aAAa;EACb,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;;EAE9C,SAAS,WAAW,gCAAgC;;;;;;;;;;;;;;;;;wBAkBtC,iBACd,OACA,UAAU,cACV,YACA,WACA,OACA,WACG,QACF,sCAAmB;yBARN"}
|
|
@@ -3,6 +3,7 @@ import { useTransitionBoundary } from "../internal/transition-boundary.mjs";
|
|
|
3
3
|
import { dataAttr } from "../internal/data-attr.mjs";
|
|
4
4
|
import { useRender } from "../internal/use-render.mjs";
|
|
5
5
|
import { guardCheckableChange, useNativeChecked } from "../internal/use-native-checked.mjs";
|
|
6
|
+
import { syncPresence } from "../internal/sync-presence.mjs";
|
|
6
7
|
import { useRadioGroupContext } from "./radio-group-context.mjs";
|
|
7
8
|
//#region src/radio-group/radio-group-item.tsx
|
|
8
9
|
/**
|
|
@@ -76,12 +77,6 @@ function RadioGroupItem({ value, disabled: disabledProp = false, transition, cla
|
|
|
76
77
|
return useTransitionBoundary("RadioGroup.Item", transition, element);
|
|
77
78
|
}
|
|
78
79
|
RadioGroupItem.displayName = "RadioGroupItem";
|
|
79
|
-
function syncPresence(el, name, present) {
|
|
80
|
-
if (el.hasAttribute(name) !== present) {
|
|
81
|
-
if (present) el.setAttribute(name, "");
|
|
82
|
-
else el.removeAttribute(name);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
80
|
//#endregion
|
|
86
81
|
export { RadioGroupItem };
|
|
87
82
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"radio-group-item.mjs","names":[],"sources":["../../src/radio-group/radio-group-item.tsx"],"sourcesContent":["\"use client\";\nimport type { ChangeEvent, ComponentPropsWithRef, CSSProperties, ViewTransitionClass } 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 { guardCheckableChange, useNativeChecked } from \"../internal/use-native-checked.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { useRadioGroupContext } from \"./radio-group-context.js\";\n\nexport interface RadioGroupItemState {\n readonly value: string;\n /** Optimistic: true as soon as the user checks this radio, before the action settles. */\n readonly checked: boolean;\n /** The Root is pending and this radio is the optimistic target. */\n readonly pending: boolean;\n /** Disabled by its own prop or by `Root disabled`. */\n readonly disabled: boolean;\n}\n\nexport interface RadioGroupItemProps extends Omit<\n ComponentPropsWithRef<\"input\">,\n | \"type\"\n | \"name\"\n | \"value\"\n | \"checked\"\n | \"defaultChecked\"\n | \"defaultValue\"\n | \"form\"\n | \"required\"\n | \"onChange\"\n | \"className\"\n | \"style\"\n | \"children\"\n> {\n /** The value this radio checks. Must be unique within the Root. Submitted as the form value. */\n value: string;\n /** Native `disabled`: unfocusable, skipped by arrow keys, never dispatches. */\n disabled?: boolean;\n /**\n * Forwarded to the Item's `<ViewTransition default>`. Default: no boundary. Fires only on the commit\n * that settles the action (or rolls it back), never on the optimistic check; animate the check\n * itself 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, RadioGroupItemState>;\n style?: StateValue<CSSProperties | undefined, RadioGroupItemState>;\n /** Replace the rendered element. Must keep an `<input>`; the library sets `type=\"radio\"`. */\n render?: RenderProp<ComponentPropsWithRef<\"input\">, RadioGroupItemState>;\n}\n\n/**\n * One `<input type=\"radio\">` of the group. Its DOM `checked` is the optimistic value\n * (`checked={optimistic === value}`): react-dom restores a controlled input to its props after\n * every event, and only the sync-lane optimistic value survives that restore.\n *\n * Owned keys (conventions 1.2; a `render` element's value is overridden): `type`, `name`,\n * `value`, `checked`, `defaultChecked` (never rendered), `disabled`, `form`, `required`,\n * `data-checked`, `data-pending`, `data-disabled`. `onChange` is merged: a `render` element's own\n * handler runs first, then the library's.\n *\n * Form reset in controlled mode: React sets an input's `defaultChecked` once, from its initial\n * `checked`. An Item added on the client while the Root's `value` equals its value gets\n * `defaultChecked` too, so two radios may carry it and a reset does not necessarily return to\n * the group's mount-time value (see `RadioGroup.Root` `value`).\n */\nexport function RadioGroupItem({\n value,\n disabled: disabledProp = false,\n transition,\n className,\n style,\n render,\n ...rest\n}: RadioGroupItemProps) {\n const ctx = useRadioGroupContext(\"RadioGroup.Item\");\n const checked = ctx.optimistic === value;\n const pending = ctx.pending && checked;\n const disabled = ctx.disabled || disabledProp;\n\n // Group mode: element check and re-apply only; the Root reports the residual paths once. The\n // hook calls `onItemMounted` at mount so that an Item mounting after its Root (a Suspense\n // boundary that hydrates later) still gets the group reconciled.\n const inputRef = useNativeChecked({\n type: \"radio\",\n component: \"RadioGroup.Item\",\n checked,\n group: true,\n onItemMounted: (el) => {\n // Hydration keeps server attributes that differ from the client render (React only warns).\n // An Item hydrating after its Root (partial hydration, Root JSDoc) can render a different\n // `checked` than the server did, e.g. after a click on an already hydrated radio, and a\n // stale `data-checked` would then stay until the prop changes again.\n if (el !== null) {\n syncPresence(el, \"data-checked\", checked);\n syncPresence(el, \"data-pending\", pending);\n syncPresence(el, \"data-disabled\", disabled);\n }\n ctx.onItemMounted(el);\n },\n defaultChecked: ctx.defaultValue === undefined ? undefined : ctx.defaultValue === value,\n });\n\n const state: RadioGroupItemState = { value, checked, pending, disabled };\n\n const element = useRender<ComponentPropsWithRef<\"input\">, RadioGroupItemState>({\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 libraryProps: {\n type: \"radio\",\n name: ctx.name,\n value,\n checked,\n defaultChecked: undefined,\n // Owned (Switch precedent) so that native disabledness, `data-disabled` and the click guard\n // agree. Only the Item's own prop: `Root disabled` reaches the radio through the fieldset.\n disabled: disabledProp,\n form: ctx.form,\n required: ctx.required,\n \"data-checked\": dataAttr(checked),\n \"data-pending\": dataAttr(pending),\n \"data-disabled\": dataAttr(disabled),\n onChange: (event: ChangeEvent<HTMLInputElement>) => {\n // A natively disabled radio fires no click; this covers `render` elements that do not\n // honour `disabled`.\n if (disabled) return;\n const nativeEvent = event.nativeEvent;\n guardCheckableChange(nativeEvent, () => ctx.select(value, nativeEvent), \"RadioGroup.Item\");\n },\n },\n });\n\n return useTransitionBoundary(\"RadioGroup.Item\", transition, element);\n}\n\nRadioGroupItem.displayName = \"RadioGroupItem\";\n
|
|
1
|
+
{"version":3,"file":"radio-group-item.mjs","names":[],"sources":["../../src/radio-group/radio-group-item.tsx"],"sourcesContent":["\"use client\";\nimport type { ChangeEvent, ComponentPropsWithRef, CSSProperties, ViewTransitionClass } from \"react\";\nimport { useTransitionBoundary } from \"../internal/transition-boundary.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport { syncPresence } from \"../internal/sync-presence.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { guardCheckableChange, useNativeChecked } from \"../internal/use-native-checked.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { useRadioGroupContext } from \"./radio-group-context.js\";\n\nexport interface RadioGroupItemState {\n readonly value: string;\n /** Optimistic: true as soon as the user checks this radio, before the action settles. */\n readonly checked: boolean;\n /** The Root is pending and this radio is the optimistic target. */\n readonly pending: boolean;\n /** Disabled by its own prop or by `Root disabled`. */\n readonly disabled: boolean;\n}\n\nexport interface RadioGroupItemProps extends Omit<\n ComponentPropsWithRef<\"input\">,\n | \"type\"\n | \"name\"\n | \"value\"\n | \"checked\"\n | \"defaultChecked\"\n | \"defaultValue\"\n | \"form\"\n | \"required\"\n | \"onChange\"\n | \"className\"\n | \"style\"\n | \"children\"\n> {\n /** The value this radio checks. Must be unique within the Root. Submitted as the form value. */\n value: string;\n /** Native `disabled`: unfocusable, skipped by arrow keys, never dispatches. */\n disabled?: boolean;\n /**\n * Forwarded to the Item's `<ViewTransition default>`. Default: no boundary. Fires only on the commit\n * that settles the action (or rolls it back), never on the optimistic check; animate the check\n * itself 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, RadioGroupItemState>;\n style?: StateValue<CSSProperties | undefined, RadioGroupItemState>;\n /** Replace the rendered element. Must keep an `<input>`; the library sets `type=\"radio\"`. */\n render?: RenderProp<ComponentPropsWithRef<\"input\">, RadioGroupItemState>;\n}\n\n/**\n * One `<input type=\"radio\">` of the group. Its DOM `checked` is the optimistic value\n * (`checked={optimistic === value}`): react-dom restores a controlled input to its props after\n * every event, and only the sync-lane optimistic value survives that restore.\n *\n * Owned keys (conventions 1.2; a `render` element's value is overridden): `type`, `name`,\n * `value`, `checked`, `defaultChecked` (never rendered), `disabled`, `form`, `required`,\n * `data-checked`, `data-pending`, `data-disabled`. `onChange` is merged: a `render` element's own\n * handler runs first, then the library's.\n *\n * Form reset in controlled mode: React sets an input's `defaultChecked` once, from its initial\n * `checked`. An Item added on the client while the Root's `value` equals its value gets\n * `defaultChecked` too, so two radios may carry it and a reset does not necessarily return to\n * the group's mount-time value (see `RadioGroup.Root` `value`).\n */\nexport function RadioGroupItem({\n value,\n disabled: disabledProp = false,\n transition,\n className,\n style,\n render,\n ...rest\n}: RadioGroupItemProps) {\n const ctx = useRadioGroupContext(\"RadioGroup.Item\");\n const checked = ctx.optimistic === value;\n const pending = ctx.pending && checked;\n const disabled = ctx.disabled || disabledProp;\n\n // Group mode: element check and re-apply only; the Root reports the residual paths once. The\n // hook calls `onItemMounted` at mount so that an Item mounting after its Root (a Suspense\n // boundary that hydrates later) still gets the group reconciled.\n const inputRef = useNativeChecked({\n type: \"radio\",\n component: \"RadioGroup.Item\",\n checked,\n group: true,\n onItemMounted: (el) => {\n // Hydration keeps server attributes that differ from the client render (React only warns).\n // An Item hydrating after its Root (partial hydration, Root JSDoc) can render a different\n // `checked` than the server did, e.g. after a click on an already hydrated radio, and a\n // stale `data-checked` would then stay until the prop changes again.\n if (el !== null) {\n syncPresence(el, \"data-checked\", checked);\n syncPresence(el, \"data-pending\", pending);\n syncPresence(el, \"data-disabled\", disabled);\n }\n ctx.onItemMounted(el);\n },\n defaultChecked: ctx.defaultValue === undefined ? undefined : ctx.defaultValue === value,\n });\n\n const state: RadioGroupItemState = { value, checked, pending, disabled };\n\n const element = useRender<ComponentPropsWithRef<\"input\">, RadioGroupItemState>({\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 libraryProps: {\n type: \"radio\",\n name: ctx.name,\n value,\n checked,\n defaultChecked: undefined,\n // Owned (Switch precedent) so that native disabledness, `data-disabled` and the click guard\n // agree. Only the Item's own prop: `Root disabled` reaches the radio through the fieldset.\n disabled: disabledProp,\n form: ctx.form,\n required: ctx.required,\n \"data-checked\": dataAttr(checked),\n \"data-pending\": dataAttr(pending),\n \"data-disabled\": dataAttr(disabled),\n onChange: (event: ChangeEvent<HTMLInputElement>) => {\n // A natively disabled radio fires no click; this covers `render` elements that do not\n // honour `disabled`.\n if (disabled) return;\n const nativeEvent = event.nativeEvent;\n guardCheckableChange(nativeEvent, () => ctx.select(value, nativeEvent), \"RadioGroup.Item\");\n },\n },\n });\n\n return useTransitionBoundary(\"RadioGroup.Item\", transition, element);\n}\n\nRadioGroupItem.displayName = \"RadioGroupItem\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAuEA,SAAgB,eAAe,EAC7B,OACA,UAAU,eAAe,OACzB,YACA,WACA,OACA,QACA,GAAG,QACmB;CACtB,MAAM,MAAM,qBAAqB,iBAAiB;CAClD,MAAM,UAAU,IAAI,eAAe;CACnC,MAAM,UAAU,IAAI,WAAW;CAC/B,MAAM,WAAW,IAAI,YAAY;CAKjC,MAAM,WAAW,iBAAiB;EAChC,MAAM;EACN,WAAW;EACX;EACA,OAAO;EACP,gBAAgB,OAAO;GAKrB,IAAI,OAAO,MAAM;IACf,aAAa,IAAI,gBAAgB,OAAO;IACxC,aAAa,IAAI,gBAAgB,OAAO;IACxC,aAAa,IAAI,iBAAiB,QAAQ;GAC5C;GACA,IAAI,cAAc,EAAE;EACtB;EACA,gBAAgB,IAAI,iBAAiB,KAAA,IAAY,KAAA,IAAY,IAAI,iBAAiB;CACpF,CAAC;CAID,MAAM,UAAU,UAA+D;EAC7E,gBAAgB;EAChB;EACA,OAAA;GALmC;GAAO;GAAS;GAAS;EAKxD;EACJ;EACA;EACA,UAAU,KAAA;EACV,KAAK;EAEL,OAAO;EACP,cAAc;GACZ,MAAM;GACN,MAAM,IAAI;GACV;GACA;GACA,gBAAgB,KAAA;GAGhB,UAAU;GACV,MAAM,IAAI;GACV,UAAU,IAAI;GACd,gBAAgB,SAAS,OAAO;GAChC,gBAAgB,SAAS,OAAO;GAChC,iBAAiB,SAAS,QAAQ;GAClC,WAAW,UAAyC;IAGlD,IAAI,UAAU;IACd,MAAM,cAAc,MAAM;IAC1B,qBAAqB,mBAAmB,IAAI,OAAO,OAAO,WAAW,GAAG,iBAAiB;GAC3F;EACF;CACF,CAAC;CAED,OAAO,sBAAsB,mBAAmB,YAAY,OAAO;AACrE;AAEA,eAAe,cAAc"}
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
import { createContext, use } from "react";
|
|
3
3
|
//#region src/tabs/tabs-context.ts
|
|
4
4
|
const TabsContext = createContext(null);
|
|
5
|
+
const TabsListContext = createContext(null);
|
|
5
6
|
function useTabsContext(part) {
|
|
6
7
|
const context = use(TabsContext);
|
|
7
8
|
if (context === null) throw new Error(`${part} must be rendered inside a Tabs.Root.`);
|
|
8
9
|
return context;
|
|
9
10
|
}
|
|
10
11
|
//#endregion
|
|
11
|
-
export { TabsContext, useTabsContext };
|
|
12
|
+
export { TabsContext, TabsListContext, useTabsContext };
|
|
12
13
|
|
|
13
14
|
//# sourceMappingURL=tabs-context.mjs.map
|
|
@@ -1 +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;
|
|
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\n/** What a Tabs.List tells its Tabs about `experimental_focusgroup`. */\nexport interface TabsListContextValue {\n /** `experimental_focusgroup` is on: the selected Tab carries `focusgroupstart`. */\n readonly focusgroup: boolean;\n /**\n * The flag is on and the engine implements `focusgroup` (detected in an effect, so false during\n * SSR and hydration). Unselected Tabs then drop `tabindex=\"-1\"`: Chromium leaves an element with\n * a negative tabindex out of the focusgroup, so roving tabindex would disable the native arrows.\n */\n readonly nativeFocusgroup: boolean;\n}\n\nexport const TabsListContext = createContext<TabsListContextValue | 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;AActE,MAAa,kBAAkB,cAA2C,IAAI;AAE9E,SAAgB,eAAe,MAAgC;CAC7D,MAAM,UAAU,IAAI,WAAW;CAC/B,IAAI,YAAY,MACd,MAAM,IAAI,MAAM,GAAG,KAAK,sCAAsC;CAEhE,OAAO;AACT"}
|
|
@@ -26,7 +26,7 @@ export interface TabsListProps extends Omit<ComponentPropsWithRef<"div">, "onKey
|
|
|
26
26
|
* `aria-orientation`, `data-orientation`, `data-disabled`, `tabIndex`, `onKeyDown`, `onFocus`,
|
|
27
27
|
* and `focusgroup` when `experimental_focusgroup` is on.
|
|
28
28
|
*/
|
|
29
|
-
export declare function TabsList({ experimental_focusgroup, className, style, children, render, ...rest }: TabsListProps):
|
|
29
|
+
export declare function TabsList({ experimental_focusgroup, className, style, children, render, ...rest }: TabsListProps): import("react").JSX.Element;
|
|
30
30
|
export declare namespace TabsList {
|
|
31
31
|
var displayName: string;
|
|
32
32
|
}
|
|
@@ -1 +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;;;;;;;;;;
|
|
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;;;;;;;;;;wBA+CpC,WACd,yBACA,WACA,OACA,UACA,WACG,QACF,gCAAa,IAAA;yBAPA"}
|
package/dist/tabs/tabs-list.mjs
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
import { dataAttr } from "../internal/data-attr.mjs";
|
|
3
3
|
import { useRender } from "../internal/use-render.mjs";
|
|
4
4
|
import { useDevWarning } from "../internal/use-dev-warning.mjs";
|
|
5
|
-
import { useTabsContext } from "./tabs-context.mjs";
|
|
6
|
-
import {
|
|
5
|
+
import { TabsListContext, useTabsContext } from "./tabs-context.mjs";
|
|
6
|
+
import { useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
7
|
+
import { jsx } from "react/jsx-runtime";
|
|
7
8
|
//#region src/tabs/tabs-list.tsx
|
|
8
9
|
function resolveKey(key, orientation, rtl) {
|
|
9
10
|
switch (key) {
|
|
@@ -23,6 +24,8 @@ function tabsOf(list) {
|
|
|
23
24
|
function focusTab(tab) {
|
|
24
25
|
tab.focus({ focusVisible: true });
|
|
25
26
|
}
|
|
27
|
+
/** Engine capabilities never change while the page lives. */
|
|
28
|
+
const subscribeNever = () => () => {};
|
|
26
29
|
/**
|
|
27
30
|
* The `role="tablist"` container. Owns arrow-key / Home / End navigation between Tabs: it only
|
|
28
31
|
* moves focus; automatic activation happens in the Tab's focus handler.
|
|
@@ -33,10 +36,11 @@ function focusTab(tab) {
|
|
|
33
36
|
*/
|
|
34
37
|
function TabsList({ experimental_focusgroup = false, className, style, children, render, ...rest }) {
|
|
35
38
|
const { orientation, loop, disabled, optimistic } = useTabsContext("Tabs.List");
|
|
36
|
-
const nativeFocusgroup =
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
const nativeFocusgroup = useSyncExternalStore(subscribeNever, () => experimental_focusgroup && "focusGroup" in HTMLElement.prototype, () => false);
|
|
40
|
+
const listContext = useMemo(() => ({
|
|
41
|
+
focusgroup: experimental_focusgroup,
|
|
42
|
+
nativeFocusgroup
|
|
43
|
+
}), [experimental_focusgroup, nativeFocusgroup]);
|
|
40
44
|
const listRef = useRef(null);
|
|
41
45
|
const [unmatched, setUnmatched] = useState(false);
|
|
42
46
|
useLayoutEffect(() => {
|
|
@@ -62,7 +66,7 @@ function TabsList({ experimental_focusgroup = false, className, style, children,
|
|
|
62
66
|
const rtl = orientation === "horizontal" && getComputedStyle(list).direction === "rtl";
|
|
63
67
|
const move = resolveKey(event.key, orientation, rtl);
|
|
64
68
|
if (move === null) return;
|
|
65
|
-
if (nativeFocusgroup
|
|
69
|
+
if (nativeFocusgroup) return;
|
|
66
70
|
event.preventDefault();
|
|
67
71
|
const tabs = tabsOf(list);
|
|
68
72
|
if (tabs.length === 0) return;
|
|
@@ -93,8 +97,8 @@ function TabsList({ experimental_focusgroup = false, className, style, children,
|
|
|
93
97
|
const first = tabs.find((tab) => tab.getAttribute("aria-disabled") !== "true") ?? tabs[0];
|
|
94
98
|
if (first !== void 0) focusTab(first);
|
|
95
99
|
};
|
|
96
|
-
const focusgroup = experimental_focusgroup ? `tablist ${orientation === "vertical" ? "block" : "inline"} ${loop ? "wrap" : "nowrap"}` : void 0;
|
|
97
|
-
|
|
100
|
+
const focusgroup = experimental_focusgroup ? `tablist ${orientation === "vertical" ? "block" : "inline"} ${loop ? "wrap" : "nowrap"} nomemory` : void 0;
|
|
101
|
+
const element = useRender({
|
|
98
102
|
defaultElement: "div",
|
|
99
103
|
render,
|
|
100
104
|
state,
|
|
@@ -114,6 +118,10 @@ function TabsList({ experimental_focusgroup = false, className, style, children,
|
|
|
114
118
|
},
|
|
115
119
|
ref: listRef
|
|
116
120
|
});
|
|
121
|
+
return /* @__PURE__ */ jsx(TabsListContext, {
|
|
122
|
+
value: listContext,
|
|
123
|
+
children: element
|
|
124
|
+
});
|
|
117
125
|
}
|
|
118
126
|
TabsList.displayName = "TabsList";
|
|
119
127
|
//#endregion
|
|
@@ -1 +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":"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"}
|
|
1
|
+
{"version":3,"file":"tabs-list.mjs","names":[],"sources":["../../src/tabs/tabs-list.tsx"],"sourcesContent":["\"use client\";\nimport { useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } 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 { TabsListContext, useTabsContext } from \"./tabs-context.js\";\nimport type { TabsListContextValue, 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/** Engine capabilities never change while the page lives. */\nconst subscribeNever = () => () => {};\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 never during SSR or hydration (conventions\n // 8): the server snapshot is `false`, so hydration markup matches the server and React re-renders\n // with the client answer afterwards. The IDL attribute is camel-cased `focusGroup`\n // (`\"focusgroup\" in HTMLElement.prototype` is always false, issue #43). The Tabs read it too:\n // they drop `tabindex=\"-1\"` on the native path (TabsListContextValue).\n const nativeFocusgroup = useSyncExternalStore(\n subscribeNever,\n () => experimental_focusgroup && \"focusGroup\" in HTMLElement.prototype,\n () => false,\n );\n const listContext = useMemo<TabsListContextValue>(\n () => ({ focusgroup: experimental_focusgroup, nativeFocusgroup }),\n [experimental_focusgroup, nativeFocusgroup],\n );\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) 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 ? // `nomemory` + `focusgroupstart` on the selected Tab: Tab always enters on the selected tab\n // (APG), not on the last focused one, which differs in manual activation.\n `tablist ${orientation === \"vertical\" ? \"block\" : \"inline\"} ${loop ? \"wrap\" : \"nowrap\"} nomemory`\n : undefined;\n\n const element = 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 return <TabsListContext value={listContext}>{element}</TabsListContext>;\n}\n\nTabsList.displayName = \"TabsList\";\n"],"mappings":"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"}
|
package/dist/tabs/tabs-tab.d.mts
CHANGED
|
@@ -38,7 +38,7 @@ export interface TabsTabProps extends Omit<ComponentPropsWithRef<"button">, "onC
|
|
|
38
38
|
* roving `tabindex`) and carries `anchor-name` for the Indicator.
|
|
39
39
|
*
|
|
40
40
|
* Owned keys (conventions 1.2): `role`, `id`, `aria-selected`, `aria-controls` (token-merged),
|
|
41
|
-
* `aria-disabled`, `tabIndex`, `data-selected`, `data-pending`, `data-disabled`,
|
|
41
|
+
* `aria-disabled`, `tabIndex`, `focusgroupstart` (with `experimental_focusgroup`), `data-selected`, `data-pending`, `data-disabled`,
|
|
42
42
|
* `data-orientation`, `onClick`, `onKeyDown`, `onFocus`.
|
|
43
43
|
*/
|
|
44
44
|
export declare function TabsTab({ value, disabled: disabledProp, transition, className, style, children, render, ...rest }: TabsTabProps): ReactNode;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tabs-tab.d.mts","names":[],"sources":["../../src/tabs/tabs-tab.tsx"],"mappings":";;;;
|
|
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"}
|
package/dist/tabs/tabs-tab.mjs
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
import { useTransitionBoundary } from "../internal/transition-boundary.mjs";
|
|
3
3
|
import { dataAttr } from "../internal/data-attr.mjs";
|
|
4
4
|
import { rendersButton, resolveStateValue, useRender } from "../internal/use-render.mjs";
|
|
5
|
-
import { useTabsContext } from "./tabs-context.mjs";
|
|
5
|
+
import { TabsListContext, useTabsContext } from "./tabs-context.mjs";
|
|
6
6
|
import { anchorName, panelId, tabId } from "./tabs-ids.mjs";
|
|
7
|
+
import { use } from "react";
|
|
7
8
|
//#region src/tabs/tabs-tab.tsx
|
|
8
9
|
function isActivationKey(event) {
|
|
9
10
|
return (event.key === "Enter" || event.key === " ") && !event.altKey && !event.ctrlKey && !event.metaKey;
|
|
@@ -19,11 +20,12 @@ function movedWithinList(event) {
|
|
|
19
20
|
* roving `tabindex`) and carries `anchor-name` for the Indicator.
|
|
20
21
|
*
|
|
21
22
|
* Owned keys (conventions 1.2): `role`, `id`, `aria-selected`, `aria-controls` (token-merged),
|
|
22
|
-
* `aria-disabled`, `tabIndex`, `data-selected`, `data-pending`, `data-disabled`,
|
|
23
|
+
* `aria-disabled`, `tabIndex`, `focusgroupstart` (with `experimental_focusgroup`), `data-selected`, `data-pending`, `data-disabled`,
|
|
23
24
|
* `data-orientation`, `onClick`, `onKeyDown`, `onFocus`.
|
|
24
25
|
*/
|
|
25
26
|
function TabsTab({ value, disabled: disabledProp = false, transition, className, style, children, render, ...rest }) {
|
|
26
27
|
const ctx = useTabsContext("Tabs.Tab");
|
|
28
|
+
const listCtx = use(TabsListContext);
|
|
27
29
|
const selected = ctx.optimistic === value;
|
|
28
30
|
const pending = ctx.pending && selected;
|
|
29
31
|
const disabled = ctx.disabled || disabledProp;
|
|
@@ -55,7 +57,8 @@ function TabsTab({ value, disabled: disabledProp = false, transition, className,
|
|
|
55
57
|
"aria-selected": selected,
|
|
56
58
|
"aria-controls": panelId(ctx.rootId, value),
|
|
57
59
|
"aria-disabled": disabled ? true : void 0,
|
|
58
|
-
tabIndex: selected ? 0 : -1,
|
|
60
|
+
tabIndex: selected ? 0 : listCtx?.nativeFocusgroup ? void 0 : -1,
|
|
61
|
+
...listCtx?.focusgroup === true && { focusgroupstart: selected ? "" : void 0 },
|
|
59
62
|
"data-selected": dataAttr(selected),
|
|
60
63
|
"data-pending": dataAttr(pending),
|
|
61
64
|
"data-disabled": dataAttr(disabled),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tabs-tab.mjs","names":[],"sources":["../../src/tabs/tabs-tab.tsx"],"sourcesContent":["\"use client\";\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 { 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`, `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 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 tabIndex: selected ? 0 : -1,\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":"
|
|
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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tooltip-popup.d.mts","names":[],"sources":["../../src/tooltip/tooltip-popup.tsx"],"mappings":";;;
|
|
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"}
|
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { dataAttr } from "../internal/data-attr.mjs";
|
|
3
3
|
import { resolveStateValue, useRender } from "../internal/use-render.mjs";
|
|
4
|
+
import { POSITION_TRY_FALLBACKS, positionArea } from "../internal/anchor-position.mjs";
|
|
4
5
|
import { useTooltipContext } from "./tooltip-context.mjs";
|
|
5
6
|
//#region src/tooltip/tooltip-popup.tsx
|
|
6
|
-
const FALLBACKS = "flip-block, flip-inline, flip-block flip-inline";
|
|
7
|
-
/** Design section 7.1: the `position-area` for each side and alignment. */
|
|
8
|
-
function positionArea(side, align) {
|
|
9
|
-
if (align === "center") return side;
|
|
10
|
-
return `${side} ${side === "top" || side === "bottom" ? align === "start" ? "span-right" : "span-left" : align === "start" ? "span-bottom" : "span-top"}`;
|
|
11
|
-
}
|
|
12
7
|
/**
|
|
13
8
|
* The tooltip content: a `div` with `popover` and `role="tooltip"`, shown in the top layer and
|
|
14
9
|
* anchored to the trigger implicitly (`interestfor` or `showPopover({ source })`). Open state is
|
|
@@ -41,7 +36,7 @@ function TooltipPopup({ side = "top", align = "center", experimental_hint = fals
|
|
|
41
36
|
className,
|
|
42
37
|
style: side === null ? style : (s) => ({
|
|
43
38
|
positionArea: positionArea(side, align),
|
|
44
|
-
positionTryFallbacks:
|
|
39
|
+
positionTryFallbacks: POSITION_TRY_FALLBACKS,
|
|
45
40
|
...resolveStateValue(style, s)
|
|
46
41
|
}),
|
|
47
42
|
children,
|
|
@@ -1 +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 { 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\
|
|
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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import { createDelayedPair } from "../internal/use-delayed-pair.mjs";
|
|
2
3
|
import { useEffect, useEffectEvent } from "react";
|
|
3
4
|
//#region src/tooltip/use-tooltip-interaction.ts
|
|
4
5
|
/**
|
|
@@ -29,13 +30,8 @@ function useTooltipInteraction(trigger, popup, options) {
|
|
|
29
30
|
if (trigger === null) return;
|
|
30
31
|
if (interestForRequested && trigger.hasAttribute("interestfor") && "interestForElement" in trigger) return;
|
|
31
32
|
const doc = trigger.ownerDocument;
|
|
32
|
-
|
|
33
|
-
const clear =
|
|
34
|
-
if (timer !== void 0) {
|
|
35
|
-
clearTimeout(timer);
|
|
36
|
-
timer = void 0;
|
|
37
|
-
}
|
|
38
|
-
};
|
|
33
|
+
const timers = createDelayedPair();
|
|
34
|
+
const clear = timers.cancel;
|
|
39
35
|
const show = () => {
|
|
40
36
|
if (isOpen(popup) || !popup.isConnected || !trigger.isConnected) return;
|
|
41
37
|
popup.showPopover({ source: trigger });
|
|
@@ -47,18 +43,10 @@ function useTooltipInteraction(trigger, popup, options) {
|
|
|
47
43
|
listenDocument(false);
|
|
48
44
|
};
|
|
49
45
|
const showLater = () => {
|
|
50
|
-
|
|
51
|
-
timer = setTimeout(() => {
|
|
52
|
-
timer = void 0;
|
|
53
|
-
show();
|
|
54
|
-
}, getDelays().delay);
|
|
46
|
+
timers.open(getDelays().delay, show);
|
|
55
47
|
};
|
|
56
48
|
const hideLater = () => {
|
|
57
|
-
|
|
58
|
-
timer = setTimeout(() => {
|
|
59
|
-
timer = void 0;
|
|
60
|
-
hide();
|
|
61
|
-
}, getDelays().closeDelay);
|
|
49
|
+
timers.close(getDelays().closeDelay, hide);
|
|
62
50
|
};
|
|
63
51
|
const hideNow = () => {
|
|
64
52
|
clear();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-tooltip-interaction.mjs","names":[],"sources":["../../src/tooltip/use-tooltip-interaction.ts"],"sourcesContent":["\"use client\";\nimport { useEffect, useEffectEvent } from \"react\";\n\nexport interface TooltipInteractionOptions {\n readonly delay: number;\n readonly closeDelay: number;\n readonly disabled: boolean;\n /** The Trigger renders `interestfor` (flag on and not disabled). Re-runs the detection. */\n readonly interestForRequested: boolean;\n}\n\n/**\n * The cross-engine JS path of design section 2.2: hover / keyboard focus with delays,\n * `showPopover({ source })`, Escape and pointerdown-anywhere. Listeners go through\n * `addEventListener` (conventions 7.4) so they can be feature-detected away: where the trigger\n * renders `interestfor` and the element implements `interestForElement`, the browser owns the\n * whole interaction and nothing is attached.\n *\n * Open state is browser-owned (conventions 3.5): every decision reads\n * `popup.matches(\":popover-open\")`, never a copy. The document listeners are attached right after\n * `showPopover()` (the `toggle` event is queued as a task, so waiting for it would leave a window\n * in which an Escape or pointerdown does nothing) and detached when `toggle` reports a close,\n * including closes the library did not perform (native light dismiss of `popover=\"hint\"`).\n */\nexport function useTooltipInteraction(\n trigger: HTMLElement | null,\n popup: HTMLElement | null,\n options: TooltipInteractionOptions,\n): void {\n const getDelays = useEffectEvent(() => ({\n delay: options.delay,\n closeDelay: options.closeDelay,\n }));\n const { disabled, interestForRequested } = options;\n\n useEffect(() => {\n if (popup === null) return;\n // Disabling while open hides the popup on both paths (design section 13, Disabled row). On the\n // JS path the previous run's cleanup already did; on the native path nothing else would, and\n // removing `interestfor` does not close an open popover.\n if (disabled) {\n if (isOpen(popup)) popup.hidePopover();\n return;\n }\n if (trigger === null) return;\n // Native path (conventions 8): only when the flag put `interestfor` on the element, and per\n // element, so a `render`ed `<span>` (no `interestForElement`) keeps the JS path. Nothing is\n // attached here, Escape included: the browser's interest handling and the `hint` close watcher\n // decide, so in Chromium an Escape that closes the tooltip may also close an enclosing modal\n // `<dialog>` (the library cannot `preventDefault()` a keydown it never sees).\n if (\n interestForRequested &&\n trigger.hasAttribute(\"interestfor\") &&\n \"interestForElement\" in trigger\n ) {\n return;\n }\n\n const doc = trigger.ownerDocument;\n
|
|
1
|
+
{"version":3,"file":"use-tooltip-interaction.mjs","names":[],"sources":["../../src/tooltip/use-tooltip-interaction.ts"],"sourcesContent":["\"use client\";\nimport { useEffect, useEffectEvent } from \"react\";\nimport { createDelayedPair } from \"../internal/use-delayed-pair.js\";\n\nexport interface TooltipInteractionOptions {\n readonly delay: number;\n readonly closeDelay: number;\n readonly disabled: boolean;\n /** The Trigger renders `interestfor` (flag on and not disabled). Re-runs the detection. */\n readonly interestForRequested: boolean;\n}\n\n/**\n * The cross-engine JS path of design section 2.2: hover / keyboard focus with delays,\n * `showPopover({ source })`, Escape and pointerdown-anywhere. Listeners go through\n * `addEventListener` (conventions 7.4) so they can be feature-detected away: where the trigger\n * renders `interestfor` and the element implements `interestForElement`, the browser owns the\n * whole interaction and nothing is attached.\n *\n * Open state is browser-owned (conventions 3.5): every decision reads\n * `popup.matches(\":popover-open\")`, never a copy. The document listeners are attached right after\n * `showPopover()` (the `toggle` event is queued as a task, so waiting for it would leave a window\n * in which an Escape or pointerdown does nothing) and detached when `toggle` reports a close,\n * including closes the library did not perform (native light dismiss of `popover=\"hint\"`).\n */\nexport function useTooltipInteraction(\n trigger: HTMLElement | null,\n popup: HTMLElement | null,\n options: TooltipInteractionOptions,\n): void {\n const getDelays = useEffectEvent(() => ({\n delay: options.delay,\n closeDelay: options.closeDelay,\n }));\n const { disabled, interestForRequested } = options;\n\n useEffect(() => {\n if (popup === null) return;\n // Disabling while open hides the popup on both paths (design section 13, Disabled row). On the\n // JS path the previous run's cleanup already did; on the native path nothing else would, and\n // removing `interestfor` does not close an open popover.\n if (disabled) {\n if (isOpen(popup)) popup.hidePopover();\n return;\n }\n if (trigger === null) return;\n // Native path (conventions 8): only when the flag put `interestfor` on the element, and per\n // element, so a `render`ed `<span>` (no `interestForElement`) keeps the JS path. Nothing is\n // attached here, Escape included: the browser's interest handling and the `hint` close watcher\n // decide, so in Chromium an Escape that closes the tooltip may also close an enclosing modal\n // `<dialog>` (the library cannot `preventDefault()` a keydown it never sees).\n if (\n interestForRequested &&\n trigger.hasAttribute(\"interestfor\") &&\n \"interestForElement\" in trigger\n ) {\n return;\n }\n\n const doc = trigger.ownerDocument;\n // One pending open or close at a time (the timer helper shared with Menu's hover intent).\n const timers = createDelayedPair();\n const clear = timers.cancel;\n const show = () => {\n if (isOpen(popup) || !popup.isConnected || !trigger.isConnected) return;\n popup.showPopover({ source: trigger });\n listenDocument(true);\n };\n const hide = () => {\n // A removed popover is already hidden (`isOpen` is false), so this never throws.\n if (!isOpen(popup)) return;\n popup.hidePopover();\n listenDocument(false);\n };\n const showLater = () => {\n timers.open(getDelays().delay, show);\n };\n const hideLater = () => {\n timers.close(getDelays().closeDelay, hide);\n };\n const hideNow = () => {\n clear();\n hide();\n };\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key !== \"Escape\") return;\n clear();\n // Escape while closed passes through untouched.\n if (!isOpen(popup)) return;\n hide();\n // This Escape was the tooltip's: an enclosing modal `<dialog>` or `popover=\"auto\"` menu\n // stays open (the close request sees a canceled keydown). A second Escape reaches them.\n event.preventDefault();\n };\n function listenDocument(listen: boolean) {\n if (listen) {\n doc.addEventListener(\"keydown\", onKeyDown);\n doc.addEventListener(\"pointerdown\", hideNow, true);\n } else {\n doc.removeEventListener(\"keydown\", onKeyDown);\n doc.removeEventListener(\"pointerdown\", hideNow, true);\n }\n }\n // Follows opens and closes the library did not perform; reads the DOM, not `newState`, so a\n // toggle task that is stale by the time it runs cannot detach the listeners of an open popup.\n const onToggle = () => {\n listenDocument(isOpen(popup));\n };\n const onTriggerEnter = (event: PointerEvent) => {\n // No hover on touch; the ARIA relation still works for screen-reader focus (design 5.3).\n if (event.pointerType !== \"touch\") showLater();\n };\n const onTriggerFocus = () => {\n // Pointer focus (a click) does not open; keyboard focus does (design 5.2).\n if (trigger.matches(\":focus-visible\")) showLater();\n };\n // The document listeners exist only while the popup is open, so before that the trigger\n // itself cancels a pending open: a click or Escape before `delay` elapses must not open the\n // tooltip afterwards. Once open, the document listeners do the hiding.\n const onTriggerKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") clear();\n };\n\n trigger.addEventListener(\"pointerenter\", onTriggerEnter);\n trigger.addEventListener(\"pointerleave\", hideLater);\n trigger.addEventListener(\"pointerdown\", clear);\n trigger.addEventListener(\"keydown\", onTriggerKeyDown);\n trigger.addEventListener(\"focus\", onTriggerFocus);\n trigger.addEventListener(\"blur\", hideLater);\n popup.addEventListener(\"pointerenter\", clear);\n popup.addEventListener(\"pointerleave\", hideLater);\n popup.addEventListener(\"toggle\", onToggle);\n\n return () => {\n hideNow();\n listenDocument(false);\n trigger.removeEventListener(\"pointerenter\", onTriggerEnter);\n trigger.removeEventListener(\"pointerleave\", hideLater);\n trigger.removeEventListener(\"pointerdown\", clear);\n trigger.removeEventListener(\"keydown\", onTriggerKeyDown);\n trigger.removeEventListener(\"focus\", onTriggerFocus);\n trigger.removeEventListener(\"blur\", hideLater);\n popup.removeEventListener(\"pointerenter\", clear);\n popup.removeEventListener(\"pointerleave\", hideLater);\n popup.removeEventListener(\"toggle\", onToggle);\n };\n }, [trigger, popup, disabled, interestForRequested]);\n}\n\n/** The popover's top-layer state, read from the DOM. A removed popover is never open. */\nfunction isOpen(popup: HTMLElement): boolean {\n return popup.isConnected && popup.matches(\":popover-open\");\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAyBA,SAAgB,sBACd,SACA,OACA,SACM;CACN,MAAM,YAAY,sBAAsB;EACtC,OAAO,QAAQ;EACf,YAAY,QAAQ;CACtB,EAAE;CACF,MAAM,EAAE,UAAU,yBAAyB;CAE3C,gBAAgB;EACd,IAAI,UAAU,MAAM;EAIpB,IAAI,UAAU;GACZ,IAAI,OAAO,KAAK,GAAG,MAAM,YAAY;GACrC;EACF;EACA,IAAI,YAAY,MAAM;EAMtB,IACE,wBACA,QAAQ,aAAa,aAAa,KAClC,wBAAwB,SAExB;EAGF,MAAM,MAAM,QAAQ;EAEpB,MAAM,SAAS,kBAAkB;EACjC,MAAM,QAAQ,OAAO;EACrB,MAAM,aAAa;GACjB,IAAI,OAAO,KAAK,KAAK,CAAC,MAAM,eAAe,CAAC,QAAQ,aAAa;GACjE,MAAM,YAAY,EAAE,QAAQ,QAAQ,CAAC;GACrC,eAAe,IAAI;EACrB;EACA,MAAM,aAAa;GAEjB,IAAI,CAAC,OAAO,KAAK,GAAG;GACpB,MAAM,YAAY;GAClB,eAAe,KAAK;EACtB;EACA,MAAM,kBAAkB;GACtB,OAAO,KAAK,UAAU,CAAC,CAAC,OAAO,IAAI;EACrC;EACA,MAAM,kBAAkB;GACtB,OAAO,MAAM,UAAU,CAAC,CAAC,YAAY,IAAI;EAC3C;EACA,MAAM,gBAAgB;GACpB,MAAM;GACN,KAAK;EACP;EAEA,MAAM,aAAa,UAAyB;GAC1C,IAAI,MAAM,QAAQ,UAAU;GAC5B,MAAM;GAEN,IAAI,CAAC,OAAO,KAAK,GAAG;GACpB,KAAK;GAGL,MAAM,eAAe;EACvB;EACA,SAAS,eAAe,QAAiB;GACvC,IAAI,QAAQ;IACV,IAAI,iBAAiB,WAAW,SAAS;IACzC,IAAI,iBAAiB,eAAe,SAAS,IAAI;GACnD,OAAO;IACL,IAAI,oBAAoB,WAAW,SAAS;IAC5C,IAAI,oBAAoB,eAAe,SAAS,IAAI;GACtD;EACF;EAGA,MAAM,iBAAiB;GACrB,eAAe,OAAO,KAAK,CAAC;EAC9B;EACA,MAAM,kBAAkB,UAAwB;GAE9C,IAAI,MAAM,gBAAgB,SAAS,UAAU;EAC/C;EACA,MAAM,uBAAuB;GAE3B,IAAI,QAAQ,QAAQ,gBAAgB,GAAG,UAAU;EACnD;EAIA,MAAM,oBAAoB,UAAyB;GACjD,IAAI,MAAM,QAAQ,UAAU,MAAM;EACpC;EAEA,QAAQ,iBAAiB,gBAAgB,cAAc;EACvD,QAAQ,iBAAiB,gBAAgB,SAAS;EAClD,QAAQ,iBAAiB,eAAe,KAAK;EAC7C,QAAQ,iBAAiB,WAAW,gBAAgB;EACpD,QAAQ,iBAAiB,SAAS,cAAc;EAChD,QAAQ,iBAAiB,QAAQ,SAAS;EAC1C,MAAM,iBAAiB,gBAAgB,KAAK;EAC5C,MAAM,iBAAiB,gBAAgB,SAAS;EAChD,MAAM,iBAAiB,UAAU,QAAQ;EAEzC,aAAa;GACX,QAAQ;GACR,eAAe,KAAK;GACpB,QAAQ,oBAAoB,gBAAgB,cAAc;GAC1D,QAAQ,oBAAoB,gBAAgB,SAAS;GACrD,QAAQ,oBAAoB,eAAe,KAAK;GAChD,QAAQ,oBAAoB,WAAW,gBAAgB;GACvD,QAAQ,oBAAoB,SAAS,cAAc;GACnD,QAAQ,oBAAoB,QAAQ,SAAS;GAC7C,MAAM,oBAAoB,gBAAgB,KAAK;GAC/C,MAAM,oBAAoB,gBAAgB,SAAS;GACnD,MAAM,oBAAoB,UAAU,QAAQ;EAC9C;CACF,GAAG;EAAC;EAAS;EAAO;EAAU;CAAoB,CAAC;AACrD;;AAGA,SAAS,OAAO,OAA6B;CAC3C,OAAO,MAAM,eAAe,MAAM,QAAQ,eAAe;AAC3D"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@funstack/ui",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.1",
|
|
4
4
|
"description": "Headless UI library for React — the first Async React-native UI library",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"components",
|
|
@@ -31,7 +31,10 @@
|
|
|
31
31
|
".": "./dist/index.mjs",
|
|
32
32
|
"./accordion": "./dist/accordion/index.mjs",
|
|
33
33
|
"./checkbox": "./dist/checkbox/index.mjs",
|
|
34
|
+
"./checkbox-group": "./dist/checkbox-group/index.mjs",
|
|
34
35
|
"./dialog": "./dist/dialog/index.mjs",
|
|
36
|
+
"./menu": "./dist/menu/index.mjs",
|
|
37
|
+
"./popover": "./dist/popover/index.mjs",
|
|
35
38
|
"./radio-group": "./dist/radio-group/index.mjs",
|
|
36
39
|
"./switch": "./dist/switch/index.mjs",
|
|
37
40
|
"./tabs": "./dist/tabs/index.mjs",
|