@intentface/ui 0.0.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +145 -2
- package/dist/ask-user/ask-user.d.ts +104 -0
- package/dist/ask-user/ask-user.js +245 -0
- package/dist/ask-user/index.d.ts +3 -0
- package/dist/ask-user/index.js +3 -0
- package/dist/ask-user/index.parts.d.ts +1 -0
- package/dist/ask-user/index.parts.js +5 -0
- package/dist/attachments/attachments.d.ts +73 -0
- package/dist/attachments/attachments.js +121 -0
- package/dist/attachments/index.d.ts +3 -0
- package/dist/attachments/index.js +3 -0
- package/dist/attachments/index.parts.d.ts +1 -0
- package/dist/attachments/index.parts.js +5 -0
- package/dist/chip/chip.d.ts +45 -0
- package/dist/chip/chip.js +43 -0
- package/dist/chip/index.d.ts +2 -0
- package/dist/chip/index.js +2 -0
- package/dist/chip/index.parts.d.ts +1 -0
- package/dist/chip/index.parts.js +5 -0
- package/dist/chip-markdown.d.ts +25 -0
- package/dist/chip-markdown.js +54 -0
- package/dist/composer/actions.d.ts +35 -0
- package/dist/composer/actions.js +76 -0
- package/dist/composer/ask-user-machine.d.ts +52 -0
- package/dist/composer/ask-user-machine.js +164 -0
- package/dist/composer/attachments-machine.d.ts +34 -0
- package/dist/composer/attachments-machine.js +91 -0
- package/dist/composer/attachments.d.ts +16 -0
- package/dist/composer/attachments.js +32 -0
- package/dist/composer/command-list.d.ts +49 -0
- package/dist/composer/command-list.js +360 -0
- package/dist/composer/commands.d.ts +28 -0
- package/dist/composer/commands.js +56 -0
- package/dist/composer/container.d.ts +3 -0
- package/dist/composer/container.js +32 -0
- package/dist/composer/controller.d.ts +3 -0
- package/dist/composer/controller.js +6 -0
- package/dist/composer/editor-dom.d.ts +57 -0
- package/dist/composer/editor-dom.js +517 -0
- package/dist/composer/fuzzy.d.ts +4 -0
- package/dist/composer/fuzzy.js +49 -0
- package/dist/composer/index.d.ts +25 -0
- package/dist/composer/index.js +13 -0
- package/dist/composer/index.parts.d.ts +11 -0
- package/dist/composer/index.parts.js +15 -0
- package/dist/composer/internals.d.ts +30 -0
- package/dist/composer/internals.js +102 -0
- package/dist/composer/keyboard.d.ts +61 -0
- package/dist/composer/keyboard.js +75 -0
- package/dist/composer/panel.d.ts +28 -0
- package/dist/composer/panel.js +76 -0
- package/dist/composer/placeholder.d.ts +10 -0
- package/dist/composer/placeholder.js +8 -0
- package/dist/composer/popover.d.ts +12 -0
- package/dist/composer/popover.js +62 -0
- package/dist/composer/prefix-detection.d.ts +23 -0
- package/dist/composer/prefix-detection.js +67 -0
- package/dist/composer/root.d.ts +21 -0
- package/dist/composer/root.js +124 -0
- package/dist/composer/segments.d.ts +108 -0
- package/dist/composer/segments.js +366 -0
- package/dist/composer/store.d.ts +98 -0
- package/dist/composer/store.js +457 -0
- package/dist/composer/textarea.d.ts +39 -0
- package/dist/composer/textarea.js +64 -0
- package/dist/composer/trigger-tracker.d.ts +12 -0
- package/dist/composer/trigger-tracker.js +90 -0
- package/dist/composer/types.d.ts +127 -0
- package/dist/composer/types.js +4 -0
- package/dist/composer/use-composer-editor.d.ts +43 -0
- package/dist/composer/use-composer-editor.js +653 -0
- package/dist/generative/catalog.d.ts +177 -0
- package/dist/generative/catalog.js +20 -0
- package/dist/generative/index.d.ts +26 -0
- package/dist/generative/index.js +20 -0
- package/dist/generative/json-schema.d.ts +45 -0
- package/dist/generative/json-schema.js +216 -0
- package/dist/generative/markdown.d.ts +21 -0
- package/dist/generative/markdown.js +76 -0
- package/dist/generative/tree.d.ts +56 -0
- package/dist/generative/tree.js +365 -0
- package/dist/generative/use-tree.d.ts +12 -0
- package/dist/generative/use-tree.js +64 -0
- package/dist/internal/change-event-details.d.ts +12 -0
- package/dist/internal/change-event-details.js +16 -0
- package/dist/internal/collapsible.d.ts +22 -0
- package/dist/internal/collapsible.js +94 -0
- package/dist/internal/collection.d.ts +32 -0
- package/dist/internal/collection.js +72 -0
- package/dist/internal/iso-layout-effect.d.ts +2 -0
- package/dist/internal/iso-layout-effect.js +6 -0
- package/dist/internal/portal.d.ts +6 -0
- package/dist/internal/portal.js +18 -0
- package/dist/internal/primitive-props.d.ts +4 -0
- package/dist/internal/primitive-props.js +3 -0
- package/dist/internal/render/baseTypes.d.ts +18 -0
- package/dist/internal/render/baseTypes.js +4 -0
- package/dist/internal/render/empty.d.ts +3 -0
- package/dist/internal/render/empty.js +5 -0
- package/dist/internal/render/getReactElementRef.d.ts +5 -0
- package/dist/internal/render/getReactElementRef.js +15 -0
- package/dist/internal/render/getStateAttributesProps.d.ts +4 -0
- package/dist/internal/render/getStateAttributesProps.js +23 -0
- package/dist/internal/render/mergeObjects.d.ts +1 -0
- package/dist/internal/render/mergeObjects.js +14 -0
- package/dist/internal/render/mergeProps.d.ts +55 -0
- package/dist/internal/render/mergeProps.js +183 -0
- package/dist/internal/render/positioning.d.ts +33 -0
- package/dist/internal/render/positioning.js +112 -0
- package/dist/internal/render/reactVersion.d.ts +3 -0
- package/dist/internal/render/reactVersion.js +7 -0
- package/dist/internal/render/resolveClassName.d.ts +8 -0
- package/dist/internal/render/resolveClassName.js +12 -0
- package/dist/internal/render/resolveStyle.d.ts +8 -0
- package/dist/internal/render/resolveStyle.js +12 -0
- package/dist/internal/render/transition.d.ts +47 -0
- package/dist/internal/render/transition.js +259 -0
- package/dist/internal/render/types.d.ts +74 -0
- package/dist/internal/render/types.js +3 -0
- package/dist/internal/render/useMergedRefs.d.ts +21 -0
- package/dist/internal/render/useMergedRefs.js +102 -0
- package/dist/internal/render/useRefWithInit.d.ts +10 -0
- package/dist/internal/render/useRefWithInit.js +12 -0
- package/dist/internal/render/useRender.d.ts +71 -0
- package/dist/internal/render/useRender.js +12 -0
- package/dist/internal/render/useRenderElement.d.ts +62 -0
- package/dist/internal/render/useRenderElement.js +157 -0
- package/dist/internal/render/warn.d.ts +1 -0
- package/dist/internal/render/warn.js +15 -0
- package/dist/internal/safe-polygon.d.ts +21 -0
- package/dist/internal/safe-polygon.js +179 -0
- package/dist/internal/scroll-into-view-within.d.ts +1 -0
- package/dist/internal/scroll-into-view-within.js +26 -0
- package/dist/internal/standard-schema.d.ts +38 -0
- package/dist/internal/standard-schema.js +4 -0
- package/dist/internal/state-mappings.d.ts +8 -0
- package/dist/internal/state-mappings.js +17 -0
- package/dist/internal/use-controlled.d.ts +19 -0
- package/dist/internal/use-controlled.js +33 -0
- package/dist/message/index.d.ts +3 -0
- package/dist/message/index.js +3 -0
- package/dist/message/index.parts.d.ts +1 -0
- package/dist/message/index.parts.js +5 -0
- package/dist/message/message.d.ts +58 -0
- package/dist/message/message.js +93 -0
- package/dist/message-utils.d.ts +55 -0
- package/dist/message-utils.js +79 -0
- package/dist/nav/index.d.ts +4 -0
- package/dist/nav/index.js +5 -0
- package/dist/nav/index.parts.d.ts +3 -0
- package/dist/nav/index.parts.js +7 -0
- package/dist/nav/nav.d.ts +152 -0
- package/dist/nav/nav.js +660 -0
- package/dist/nav/store.d.ts +50 -0
- package/dist/nav/store.js +95 -0
- package/dist/reasoning/index.d.ts +3 -0
- package/dist/reasoning/index.js +3 -0
- package/dist/reasoning/index.parts.d.ts +1 -0
- package/dist/reasoning/index.parts.js +5 -0
- package/dist/reasoning/reasoning.d.ts +38 -0
- package/dist/reasoning/reasoning.js +67 -0
- package/dist/shell/index.d.ts +5 -0
- package/dist/shell/index.js +5 -0
- package/dist/shell/index.parts.d.ts +3 -0
- package/dist/shell/index.parts.js +7 -0
- package/dist/shell/shell.d.ts +108 -0
- package/dist/shell/shell.js +390 -0
- package/dist/shell/store.d.ts +112 -0
- package/dist/shell/store.js +134 -0
- package/dist/steps/index.d.ts +2 -0
- package/dist/steps/index.js +2 -0
- package/dist/steps/index.parts.d.ts +1 -0
- package/dist/steps/index.parts.js +5 -0
- package/dist/steps/steps.d.ts +90 -0
- package/dist/steps/steps.js +116 -0
- package/dist/tabs/anchored.d.ts +59 -0
- package/dist/tabs/anchored.js +109 -0
- package/dist/tabs/hover.d.ts +33 -0
- package/dist/tabs/hover.js +169 -0
- package/dist/tabs/index.d.ts +5 -0
- package/dist/tabs/index.js +4 -0
- package/dist/tabs/index.parts.d.ts +4 -0
- package/dist/tabs/index.parts.js +8 -0
- package/dist/tabs/store.d.ts +131 -0
- package/dist/tabs/store.js +248 -0
- package/dist/tabs/tabs.d.ts +217 -0
- package/dist/tabs/tabs.js +458 -0
- package/dist/thread/geometry.d.ts +23 -0
- package/dist/thread/geometry.js +48 -0
- package/dist/thread/index.d.ts +4 -0
- package/dist/thread/index.js +3 -0
- package/dist/thread/index.parts.d.ts +1 -0
- package/dist/thread/index.parts.js +5 -0
- package/dist/thread/stores.d.ts +21 -0
- package/dist/thread/stores.js +63 -0
- package/dist/thread/thread.d.ts +57 -0
- package/dist/thread/thread.js +663 -0
- package/dist/types.d.ts +58 -0
- package/dist/types.js +17 -0
- package/package.json +120 -4
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { type ComponentProps } from "react";
|
|
2
|
+
import { Collapsible } from "../internal/collapsible.js";
|
|
3
|
+
import type { PrimitiveProps } from "../internal/primitive-props.js";
|
|
4
|
+
export type StepsRootProps = PrimitiveProps<"div">;
|
|
5
|
+
/**
|
|
6
|
+
* The timeline container. Ships the disclosure and status plumbing and no row
|
|
7
|
+
* content: what a tool call or a reasoning step looks like is yours.
|
|
8
|
+
* Renders a `<div>` element.
|
|
9
|
+
*/
|
|
10
|
+
export declare const StepsRoot: {
|
|
11
|
+
({ className, render, style, ...elementProps }: StepsRootProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
12
|
+
displayName: string;
|
|
13
|
+
};
|
|
14
|
+
export type StepStatus = string;
|
|
15
|
+
export type StepsItemProps = ComponentProps<typeof Collapsible> & {
|
|
16
|
+
status?: StepStatus;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* One node of the tree, and the unit that nests: an item's panel may hold
|
|
20
|
+
* further items, so a timeline goes as deep as the run did. Publishes its
|
|
21
|
+
* `status` to the icon and label through context.
|
|
22
|
+
* Renders a `<div>` element.
|
|
23
|
+
*/
|
|
24
|
+
export declare const StepsItem: {
|
|
25
|
+
({ status, defaultOpen, ...props }: StepsItemProps): import("react").JSX.Element;
|
|
26
|
+
displayName: string;
|
|
27
|
+
};
|
|
28
|
+
export type StepsTriggerProps = ComponentProps<typeof Collapsible.Trigger>;
|
|
29
|
+
/**
|
|
30
|
+
* The row that expands an item. A real button, so the tree is plain sequential
|
|
31
|
+
* tab order rather than a composite widget with its own keyboard model.
|
|
32
|
+
* Renders a `<button>` element.
|
|
33
|
+
*/
|
|
34
|
+
export declare const StepsTrigger: {
|
|
35
|
+
(props: StepsTriggerProps): import("react").JSX.Element;
|
|
36
|
+
displayName: string;
|
|
37
|
+
};
|
|
38
|
+
export type StepsPanelProps = ComponentProps<typeof Collapsible.Panel>;
|
|
39
|
+
/**
|
|
40
|
+
* The collapsible body. Publishes its measured height while a transition runs
|
|
41
|
+
* and releases it once settled open, so an open panel grows with content that
|
|
42
|
+
* arrives inside it.
|
|
43
|
+
* Renders a `<div>` element.
|
|
44
|
+
*/
|
|
45
|
+
export declare const StepsPanel: {
|
|
46
|
+
(props: StepsPanelProps): import("react").JSX.Element;
|
|
47
|
+
displayName: string;
|
|
48
|
+
};
|
|
49
|
+
export type StepsIconProps = PrimitiveProps<"span", {
|
|
50
|
+
status: StepStatus;
|
|
51
|
+
}> & {
|
|
52
|
+
status?: StepStatus;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* The row's status glyph, `aria-hidden` because colour and shape announce
|
|
56
|
+
* nothing. Resolves `status` from its own prop, then the enclosing item.
|
|
57
|
+
* Renders a `<span>` element.
|
|
58
|
+
*/
|
|
59
|
+
export declare const StepsIcon: {
|
|
60
|
+
({ status, className, render, style, ...elementProps }: StepsIconProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
61
|
+
displayName: string;
|
|
62
|
+
};
|
|
63
|
+
export type StepsLabelProps = PrimitiveProps<"span", {
|
|
64
|
+
status: StepStatus;
|
|
65
|
+
}> & {
|
|
66
|
+
status?: StepStatus;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* The row's text. Resolves `status` the same way the icon does, so one
|
|
70
|
+
* attribute drives both.
|
|
71
|
+
* Renders a `<span>` element.
|
|
72
|
+
*/
|
|
73
|
+
export declare const StepsLabel: {
|
|
74
|
+
({ status, className, render, style, ...elementProps }: StepsLabelProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
75
|
+
displayName: string;
|
|
76
|
+
};
|
|
77
|
+
export type StepsStatusProps = PrimitiveProps<"span", {
|
|
78
|
+
status: StepStatus;
|
|
79
|
+
}> & {
|
|
80
|
+
status?: StepStatus;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* The only part that speaks the status. Renders a visually hidden span saying
|
|
84
|
+
* the resolved status string; pass `children` to localise the wording.
|
|
85
|
+
* Renders a `<span>` element.
|
|
86
|
+
*/
|
|
87
|
+
export declare const StepsStatus: {
|
|
88
|
+
({ status, children, className, render, style, ...elementProps }: StepsStatusProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
89
|
+
displayName: string;
|
|
90
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
// Headless steps timeline — a recursive disclosure tree. Every node is the
|
|
4
|
+
// same part (Item = a Collapsible; Trigger/Panel scope to the nearest Item by
|
|
5
|
+
// nesting), so timelines nest arbitrarily: a panel can contain further items.
|
|
6
|
+
// State surfaces as data attributes only: data-status per item, data-open /
|
|
7
|
+
// data-closed from the disclosure, and data-nested on items that sit inside
|
|
8
|
+
// another item (for rail/connector styling; sibling facts are CSS's job via
|
|
9
|
+
// :last-child / :only-child). Icons, connectors, markdown, and all classes
|
|
10
|
+
// belong to the styled layer; tool derivation belongs to the app layer.
|
|
11
|
+
import { createContext, use } from "react";
|
|
12
|
+
import { Collapsible } from "../internal/collapsible.js";
|
|
13
|
+
import { useRenderElement } from "../internal/render/useRenderElement.js";
|
|
14
|
+
// Visually-hidden-but-announced (screen-reader-only) default for Steps.Status.
|
|
15
|
+
const visuallyHiddenStyle = {
|
|
16
|
+
position: "absolute",
|
|
17
|
+
width: 1,
|
|
18
|
+
height: 1,
|
|
19
|
+
margin: -1,
|
|
20
|
+
padding: 0,
|
|
21
|
+
overflow: "hidden",
|
|
22
|
+
clipPath: "inset(50%)",
|
|
23
|
+
whiteSpace: "nowrap",
|
|
24
|
+
border: 0,
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* The timeline container. Ships the disclosure and status plumbing and no row
|
|
28
|
+
* content: what a tool call or a reasoning step looks like is yours.
|
|
29
|
+
* Renders a `<div>` element.
|
|
30
|
+
*/
|
|
31
|
+
export const StepsRoot = ({ className, render, style, ...elementProps }) => useRenderElement("div", { className, render, style }, { props: [{ "data-steps": "" }, elementProps] });
|
|
32
|
+
StepsRoot.displayName = "Steps";
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Item — one disclosure node. Presence-nested: items inside another item
|
|
35
|
+
// carry data-nested.
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// True inside any Item, so deeper items can surface data-nested.
|
|
38
|
+
const NestedContext = createContext(false);
|
|
39
|
+
// Status inherited by Icon / Label from the nearest Item.
|
|
40
|
+
const StepStatusContext = createContext(null);
|
|
41
|
+
const useStepStatus = (status) => {
|
|
42
|
+
const inherited = use(StepStatusContext);
|
|
43
|
+
return status ?? inherited ?? "complete";
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* One node of the tree, and the unit that nests: an item's panel may hold
|
|
47
|
+
* further items, so a timeline goes as deep as the run did. Publishes its
|
|
48
|
+
* `status` to the icon and label through context.
|
|
49
|
+
* Renders a `<div>` element.
|
|
50
|
+
*/
|
|
51
|
+
export const StepsItem = ({ status = "complete", defaultOpen, ...props }) => {
|
|
52
|
+
const isNested = use(NestedContext);
|
|
53
|
+
return (_jsx(NestedContext, { value: true, children: _jsx(StepStatusContext, { value: status, children: _jsx(Collapsible, { defaultOpen: defaultOpen ?? status === "active", "aria-current": status === "active" ? "step" : undefined, "data-steps-item": "", "data-status": status, "data-nested": isNested || undefined, ...props }) }) }));
|
|
54
|
+
};
|
|
55
|
+
StepsItem.displayName = "StepsItem";
|
|
56
|
+
/**
|
|
57
|
+
* The row that expands an item. A real button, so the tree is plain sequential
|
|
58
|
+
* tab order rather than a composite widget with its own keyboard model.
|
|
59
|
+
* Renders a `<button>` element.
|
|
60
|
+
*/
|
|
61
|
+
export const StepsTrigger = (props) => (_jsx(Collapsible.Trigger, { "data-steps-trigger": "", ...props }));
|
|
62
|
+
StepsTrigger.displayName = "StepsTrigger";
|
|
63
|
+
/**
|
|
64
|
+
* The collapsible body. Publishes its measured height while a transition runs
|
|
65
|
+
* and releases it once settled open, so an open panel grows with content that
|
|
66
|
+
* arrives inside it.
|
|
67
|
+
* Renders a `<div>` element.
|
|
68
|
+
*/
|
|
69
|
+
export const StepsPanel = (props) => (_jsx(Collapsible.Panel, { "data-steps-panel": "", ...props }));
|
|
70
|
+
StepsPanel.displayName = "StepsPanel";
|
|
71
|
+
/**
|
|
72
|
+
* The row's status glyph, `aria-hidden` because colour and shape announce
|
|
73
|
+
* nothing. Resolves `status` from its own prop, then the enclosing item.
|
|
74
|
+
* Renders a `<span>` element.
|
|
75
|
+
*/
|
|
76
|
+
export const StepsIcon = ({ status, className, render, style, ...elementProps }) => {
|
|
77
|
+
const resolvedStatus = useStepStatus(status);
|
|
78
|
+
return useRenderElement("span", { className, render, style }, {
|
|
79
|
+
state: { status: resolvedStatus },
|
|
80
|
+
props: [{ "data-steps-icon": "", "aria-hidden": true }, elementProps],
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
StepsIcon.displayName = "StepsIcon";
|
|
84
|
+
/**
|
|
85
|
+
* The row's text. Resolves `status` the same way the icon does, so one
|
|
86
|
+
* attribute drives both.
|
|
87
|
+
* Renders a `<span>` element.
|
|
88
|
+
*/
|
|
89
|
+
export const StepsLabel = ({ status, className, render, style, ...elementProps }) => {
|
|
90
|
+
const resolvedStatus = useStepStatus(status);
|
|
91
|
+
return useRenderElement("span", { className, render, style }, {
|
|
92
|
+
state: { status: resolvedStatus },
|
|
93
|
+
props: [{ "data-steps-label": "" }, elementProps],
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
StepsLabel.displayName = "StepsLabel";
|
|
97
|
+
/**
|
|
98
|
+
* The only part that speaks the status. Renders a visually hidden span saying
|
|
99
|
+
* the resolved status string; pass `children` to localise the wording.
|
|
100
|
+
* Renders a `<span>` element.
|
|
101
|
+
*/
|
|
102
|
+
export const StepsStatus = ({ status, children, className, render, style, ...elementProps }) => {
|
|
103
|
+
const resolvedStatus = useStepStatus(status);
|
|
104
|
+
return useRenderElement("span", { className, render, style }, {
|
|
105
|
+
state: { status: resolvedStatus },
|
|
106
|
+
props: [
|
|
107
|
+
{
|
|
108
|
+
"data-steps-status": "",
|
|
109
|
+
style: visuallyHiddenStyle,
|
|
110
|
+
children: children ?? resolvedStatus,
|
|
111
|
+
},
|
|
112
|
+
elementProps,
|
|
113
|
+
],
|
|
114
|
+
});
|
|
115
|
+
};
|
|
116
|
+
StepsStatus.displayName = "StepsStatus";
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { PrimitiveProps } from "../internal/primitive-props.js";
|
|
3
|
+
import type { AnchorAlign, AnchorSide } from "../internal/render/positioning.js";
|
|
4
|
+
import { type TransitionStatus } from "../internal/render/transition.js";
|
|
5
|
+
/**
|
|
6
|
+
* The floating half of the surface.
|
|
7
|
+
*
|
|
8
|
+
* Wrapping `Tabs.Viewport` in these three parts is the entire difference
|
|
9
|
+
* between a panel that sits in the layout and one that floats above the open
|
|
10
|
+
* tab. Nothing else changes — same Root, same List, same Panels, same ARIA —
|
|
11
|
+
* because the panels portal into the viewport wherever it happens to be:
|
|
12
|
+
*
|
|
13
|
+
* List + Viewport in the layout
|
|
14
|
+
* List + Portal > Positioner > Popup > Viewport anchored to the open tab
|
|
15
|
+
*
|
|
16
|
+
* One positioner serves the whole collection, anchored to whichever tab is
|
|
17
|
+
* open. A positioner per tab would mean one `autoUpdate` loop each — a
|
|
18
|
+
* ResizeObserver and an IntersectionObserver apiece, still running while
|
|
19
|
+
* closed — and nothing to morph between when the selection moves.
|
|
20
|
+
*
|
|
21
|
+
* Non-modal throughout: no backdrop, no scroll lock, no focus trap, and no
|
|
22
|
+
* dismissal on outside press. A chat dock is something you work *behind*.
|
|
23
|
+
*/
|
|
24
|
+
export type TabsSide = AnchorSide;
|
|
25
|
+
export type TabsAlign = AnchorAlign;
|
|
26
|
+
export type TabsPositionerState = {
|
|
27
|
+
side: TabsSide;
|
|
28
|
+
align: TabsAlign;
|
|
29
|
+
open: boolean;
|
|
30
|
+
};
|
|
31
|
+
export type TabsPortalProps = {
|
|
32
|
+
children?: ReactNode;
|
|
33
|
+
/** Defaults to `document.body`. */
|
|
34
|
+
container?: HTMLElement | null;
|
|
35
|
+
/**
|
|
36
|
+
* Keep the surface in the DOM while nothing is open. Costs a live
|
|
37
|
+
* positioning loop, so reach for it only when something inside must survive
|
|
38
|
+
* being closed.
|
|
39
|
+
*/
|
|
40
|
+
keepMounted?: boolean;
|
|
41
|
+
};
|
|
42
|
+
export declare const TabsPortal: ({ children, container, keepMounted }: TabsPortalProps) => import("react").JSX.Element | null;
|
|
43
|
+
export type TabsPositionerProps = PrimitiveProps<"div", TabsPositionerState> & {
|
|
44
|
+
side?: TabsSide;
|
|
45
|
+
align?: TabsAlign;
|
|
46
|
+
sideOffset?: number;
|
|
47
|
+
/** Kept clear of the viewport edges by this much. */
|
|
48
|
+
collisionPadding?: number;
|
|
49
|
+
children?: ReactNode;
|
|
50
|
+
};
|
|
51
|
+
export declare const TabsPositioner: ({ side, align, sideOffset, collisionPadding, className, render, style, ...elementProps }: TabsPositionerProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
52
|
+
export type TabsPopupState = {
|
|
53
|
+
open: boolean;
|
|
54
|
+
/** Drives `data-starting-style` / `data-ending-style` for the surface itself. */
|
|
55
|
+
transitionStatus: TransitionStatus;
|
|
56
|
+
};
|
|
57
|
+
export type TabsPopupProps = PrimitiveProps<"div", TabsPopupState>;
|
|
58
|
+
/** The surface itself: yours to style and animate. */
|
|
59
|
+
export declare const TabsPopup: ({ className, render, style, ...elementProps }: TabsPopupProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, use, useCallback, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { useIsomorphicLayoutEffect } from "../internal/iso-layout-effect.js";
|
|
5
|
+
import { Portal } from "../internal/portal.js";
|
|
6
|
+
import { useAnchorPositioning } from "../internal/render/positioning.js";
|
|
7
|
+
import { useAnimationsFinished, useTransitionStatus, } from "../internal/render/transition.js";
|
|
8
|
+
import { useRenderElement } from "../internal/render/useRenderElement.js";
|
|
9
|
+
import { openStateMapping, transitionStatusMapping } from "../internal/state-mappings.js";
|
|
10
|
+
import { useTabsContextStore, useTabsStore } from "./store.js";
|
|
11
|
+
const selectValue = (tabs) => tabs.value;
|
|
12
|
+
const SurfaceTransitionContext = createContext(null);
|
|
13
|
+
export const TabsPortal = ({ children, container, keepMounted = false }) => {
|
|
14
|
+
const store = useTabsContextStore();
|
|
15
|
+
const open = useTabsStore(store, selectValue) !== null;
|
|
16
|
+
// Kept mounted through the exit, so the surface can animate away instead of
|
|
17
|
+
// blinking out. Popup reports when that has finished.
|
|
18
|
+
const { mounted, setMounted, transitionStatus } = useTransitionStatus(open);
|
|
19
|
+
const transition = useMemo(() => ({ transitionStatus, open, onExitFinished: () => setMounted(false) }), [transitionStatus, open, setMounted]);
|
|
20
|
+
if (!mounted && !keepMounted)
|
|
21
|
+
return null;
|
|
22
|
+
return (_jsx(Portal, { container: container, children: _jsx(SurfaceTransitionContext, { value: transition, children: children }) }));
|
|
23
|
+
};
|
|
24
|
+
export const TabsPositioner = ({ side = "top", align = "center", sideOffset = 8, collisionPadding = 8, className, render, style, ...elementProps }) => {
|
|
25
|
+
const store = useTabsContextStore();
|
|
26
|
+
const value = useTabsStore(store, selectValue);
|
|
27
|
+
const open = value !== null;
|
|
28
|
+
const positionerRef = useRef(null);
|
|
29
|
+
// The open tab is the anchor — its whole item where there is one, falling
|
|
30
|
+
// back to the button itself. Read on demand rather than subscribed to, so a
|
|
31
|
+
// tab mounted in the same commit has registered by the time this runs.
|
|
32
|
+
const getAnchor = useCallback(() => (value === null ? null : (store.anchors.get(value) ?? store.elements.get(value) ?? null)), [store, value]);
|
|
33
|
+
/*
|
|
34
|
+
* The resolved placement, which is what flip and shift may have changed it
|
|
35
|
+
* to — so a popup that flipped can style itself as the side it ended up on.
|
|
36
|
+
* `null` until the anchor has been measured, a frame later; the hook itself
|
|
37
|
+
* withholds paint until that lands.
|
|
38
|
+
*/
|
|
39
|
+
const [placement, setPlacement] = useState(null);
|
|
40
|
+
// Fires on every reposition, including scrolls that change nothing, so only
|
|
41
|
+
// a real change is allowed through to state.
|
|
42
|
+
const onPositioned = useCallback((next) => {
|
|
43
|
+
setPlacement((current) => current && current.side === next.side && current.align === next.align ? current : next);
|
|
44
|
+
}, []);
|
|
45
|
+
useAnchorPositioning(getAnchor, positionerRef, {
|
|
46
|
+
enabled: open,
|
|
47
|
+
side,
|
|
48
|
+
align,
|
|
49
|
+
sideOffset,
|
|
50
|
+
collisionPadding,
|
|
51
|
+
onPositioned,
|
|
52
|
+
});
|
|
53
|
+
return useRenderElement("div", { className, render, style }, {
|
|
54
|
+
state: { side: placement?.side ?? side, align: placement?.align ?? align, open },
|
|
55
|
+
stateAttributesMapping: {
|
|
56
|
+
...openStateMapping,
|
|
57
|
+
// data-side / data-align are written imperatively by useAnchorPositioning, so keep the
|
|
58
|
+
// state copy out of the DOM rather than fighting it for the same attributes.
|
|
59
|
+
side: () => null,
|
|
60
|
+
align: () => null,
|
|
61
|
+
},
|
|
62
|
+
ref: positionerRef,
|
|
63
|
+
props: [
|
|
64
|
+
{
|
|
65
|
+
"data-tabs-positioner": "",
|
|
66
|
+
// Positioning belongs to this element and styling to the popup inside
|
|
67
|
+
// it, so the thing being animated is never the thing being moved.
|
|
68
|
+
role: "presentation",
|
|
69
|
+
// Absolute from the first render, so it never spends a frame as a
|
|
70
|
+
// layout participant in whatever it is portaled into. left/top/
|
|
71
|
+
// visibility stay imperative — a React-managed value that *changed*
|
|
72
|
+
// would be cleared out from under the hook; this one never does.
|
|
73
|
+
style: { position: "absolute" },
|
|
74
|
+
},
|
|
75
|
+
elementProps,
|
|
76
|
+
],
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
/** The surface itself: yours to style and animate. */
|
|
80
|
+
export const TabsPopup = ({ className, render, style, ...elementProps }) => {
|
|
81
|
+
const store = useTabsContextStore();
|
|
82
|
+
const open = useTabsStore(store, selectValue) !== null;
|
|
83
|
+
const transition = use(SurfaceTransitionContext);
|
|
84
|
+
const popupRef = useRef(null);
|
|
85
|
+
const runWhenAnimationsFinish = useAnimationsFinished(popupRef);
|
|
86
|
+
useIsomorphicLayoutEffect(() => {
|
|
87
|
+
if (!transition || transition.open)
|
|
88
|
+
return;
|
|
89
|
+
const controller = new AbortController();
|
|
90
|
+
runWhenAnimationsFinish(transition.onExitFinished, controller.signal);
|
|
91
|
+
return () => controller.abort();
|
|
92
|
+
}, [transition, runWhenAnimationsFinish]);
|
|
93
|
+
return useRenderElement("div", { className, render, style }, {
|
|
94
|
+
state: { open, transitionStatus: transition?.transitionStatus },
|
|
95
|
+
stateAttributesMapping: { ...openStateMapping, ...transitionStatusMapping },
|
|
96
|
+
ref: [popupRef, store.popupRef],
|
|
97
|
+
props: [
|
|
98
|
+
{
|
|
99
|
+
"data-tabs-popup": "",
|
|
100
|
+
onPointerEnter: store.hover.enterPopup,
|
|
101
|
+
onPointerLeave: store.hover.leavePopup,
|
|
102
|
+
// A press or focus inside means it is in use, so the mouse leaving no longer closes it.
|
|
103
|
+
onPointerDown: store.hover.claim,
|
|
104
|
+
onFocus: store.hover.claim,
|
|
105
|
+
},
|
|
106
|
+
elementProps,
|
|
107
|
+
],
|
|
108
|
+
});
|
|
109
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { PointerEvent } from "react";
|
|
2
|
+
import type { TabsRootChangeEventReason, TabsStore } from "./store.js";
|
|
3
|
+
/** A tab the mouse is over, with its own timing. */
|
|
4
|
+
export type TabsHoverTarget = {
|
|
5
|
+
value: string;
|
|
6
|
+
openDelay: number;
|
|
7
|
+
closeDelay: number;
|
|
8
|
+
};
|
|
9
|
+
type HoverStore = Pick<TabsStore, "getSnapshot" | "selectWithDetails" | "popupRef">;
|
|
10
|
+
/**
|
|
11
|
+
* Hover for the whole strip, decided in one place. Every `openOnHover` tab and
|
|
12
|
+
* the popup report to it, so moving between tabs hands the popup over instead
|
|
13
|
+
* of closing it and opening it again.
|
|
14
|
+
*/
|
|
15
|
+
export declare const createTabsHover: (store: HoverStore) => {
|
|
16
|
+
/** Lets leaving one hover tab for another hand over. Returns the unregister. */
|
|
17
|
+
register: (tab: Element) => () => void;
|
|
18
|
+
/** Drops anything pending, for when the strip unmounts. */
|
|
19
|
+
release: () => void;
|
|
20
|
+
owns: (value: string) => boolean;
|
|
21
|
+
/** A change landed: hover owns it only if hover made it. */
|
|
22
|
+
changed: (value: string | null, reason: TabsRootChangeEventReason) => void;
|
|
23
|
+
enterTab: (next: TabsHoverTarget, event: PointerEvent) => void;
|
|
24
|
+
/** Moving restarts the wait, so the tab opens once the mouse comes to rest. */
|
|
25
|
+
moveOnTab: (event: PointerEvent) => void;
|
|
26
|
+
leaveTab: (event: PointerEvent) => void;
|
|
27
|
+
enterPopup: (event: PointerEvent) => void;
|
|
28
|
+
leavePopup: (event: PointerEvent) => void;
|
|
29
|
+
/** A press on the tab, or a press or focus inside the popup: it is in use now. */
|
|
30
|
+
claim: () => void;
|
|
31
|
+
};
|
|
32
|
+
export type TabsHover = ReturnType<typeof createTabsHover>;
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { flushSync } from "react-dom";
|
|
3
|
+
import { createChangeEventDetails } from "../internal/change-event-details.js";
|
|
4
|
+
import { createSafePolygon } from "../internal/safe-polygon.js";
|
|
5
|
+
const isMouse = (event) => event.pointerType === "mouse";
|
|
6
|
+
const isInside = (elements, target) => {
|
|
7
|
+
if (!(target instanceof Node))
|
|
8
|
+
return false;
|
|
9
|
+
for (const element of elements)
|
|
10
|
+
if (element.contains(target))
|
|
11
|
+
return true;
|
|
12
|
+
return false;
|
|
13
|
+
};
|
|
14
|
+
/** The popup's side of the tab, read off where both actually are. */
|
|
15
|
+
const sideOf = (tab, popup) => {
|
|
16
|
+
if (popup.top >= tab.bottom)
|
|
17
|
+
return "bottom";
|
|
18
|
+
if (popup.bottom <= tab.top)
|
|
19
|
+
return "top";
|
|
20
|
+
return popup.left >= tab.right ? "right" : "left";
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Hover for the whole strip, decided in one place. Every `openOnHover` tab and
|
|
24
|
+
* the popup report to it, so moving between tabs hands the popup over instead
|
|
25
|
+
* of closing it and opening it again.
|
|
26
|
+
*/
|
|
27
|
+
export const createTabsHover = (store) => {
|
|
28
|
+
const tabs = new Set();
|
|
29
|
+
/** The tab hover opened, until anything else changes the selection or a press claims it. */
|
|
30
|
+
let owner = null;
|
|
31
|
+
let target = null;
|
|
32
|
+
let openTimer;
|
|
33
|
+
let closeTimer;
|
|
34
|
+
let stopCone = () => { };
|
|
35
|
+
/** No pending open or close, and no cone. */
|
|
36
|
+
const release = () => {
|
|
37
|
+
clearTimeout(openTimer);
|
|
38
|
+
clearTimeout(closeTimer);
|
|
39
|
+
openTimer = undefined;
|
|
40
|
+
stopCone();
|
|
41
|
+
};
|
|
42
|
+
/** The popup is open or still fading out, so the next tab takes it over at once. */
|
|
43
|
+
const onScreen = () => {
|
|
44
|
+
const popup = store.popupRef.current;
|
|
45
|
+
return (popup !== null &&
|
|
46
|
+
(!popup.hasAttribute("data-closed") || popup.hasAttribute("data-ending-style")));
|
|
47
|
+
};
|
|
48
|
+
/** Redrawn at once, so a controlled parent is current before the mouse moves again. */
|
|
49
|
+
const change = (value, event, tab) => flushSync(() => store.selectWithDetails(value, createChangeEventDetails("trigger-hover", event, tab)));
|
|
50
|
+
const open = (value, event, tab) => {
|
|
51
|
+
const { value: current } = store.getSnapshot();
|
|
52
|
+
// Hover never takes over a tab someone opened on purpose.
|
|
53
|
+
if (current === value || (current !== null && owner === null))
|
|
54
|
+
return;
|
|
55
|
+
change(value, event, tab);
|
|
56
|
+
};
|
|
57
|
+
const close = (event) => {
|
|
58
|
+
release();
|
|
59
|
+
if (owner !== null)
|
|
60
|
+
change(null, event);
|
|
61
|
+
};
|
|
62
|
+
const closeAfterDelay = (event) => {
|
|
63
|
+
clearTimeout(closeTimer);
|
|
64
|
+
const delay = target?.closeDelay ?? 0;
|
|
65
|
+
if (delay > 0)
|
|
66
|
+
closeTimer = setTimeout(() => close(event), delay);
|
|
67
|
+
else
|
|
68
|
+
close(event);
|
|
69
|
+
};
|
|
70
|
+
const openAfterRest = (event, tab) => {
|
|
71
|
+
if (!target)
|
|
72
|
+
return;
|
|
73
|
+
const { value, openDelay } = target;
|
|
74
|
+
clearTimeout(openTimer);
|
|
75
|
+
openTimer = setTimeout(() => {
|
|
76
|
+
openTimer = undefined;
|
|
77
|
+
open(value, event, tab);
|
|
78
|
+
}, openDelay);
|
|
79
|
+
};
|
|
80
|
+
/** Keeps the popup open while the mouse travels from the tab towards it. */
|
|
81
|
+
const armCone = (tab, event) => {
|
|
82
|
+
stopCone();
|
|
83
|
+
const popup = store.popupRef.current;
|
|
84
|
+
if (!popup)
|
|
85
|
+
return close(event);
|
|
86
|
+
const cone = createSafePolygon({
|
|
87
|
+
x: event.clientX,
|
|
88
|
+
y: event.clientY,
|
|
89
|
+
side: sideOf(tab.getBoundingClientRect(), popup.getBoundingClientRect()),
|
|
90
|
+
reference: tab,
|
|
91
|
+
floating: popup,
|
|
92
|
+
onClose: () => closeAfterDelay(event),
|
|
93
|
+
});
|
|
94
|
+
document.addEventListener("mousemove", cone.onMouseMove);
|
|
95
|
+
stopCone = () => {
|
|
96
|
+
cone.stop();
|
|
97
|
+
document.removeEventListener("mousemove", cone.onMouseMove);
|
|
98
|
+
stopCone = () => { };
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
return {
|
|
102
|
+
/** Lets leaving one hover tab for another hand over. Returns the unregister. */
|
|
103
|
+
register: (tab) => {
|
|
104
|
+
tabs.add(tab);
|
|
105
|
+
return () => {
|
|
106
|
+
tabs.delete(tab);
|
|
107
|
+
release();
|
|
108
|
+
};
|
|
109
|
+
},
|
|
110
|
+
/** Drops anything pending, for when the strip unmounts. */
|
|
111
|
+
release,
|
|
112
|
+
owns: (value) => owner === value,
|
|
113
|
+
/** A change landed: hover owns it only if hover made it. */
|
|
114
|
+
changed: (value, reason) => {
|
|
115
|
+
owner = reason === "trigger-hover" ? value : null;
|
|
116
|
+
if (reason !== "trigger-hover")
|
|
117
|
+
release();
|
|
118
|
+
},
|
|
119
|
+
enterTab: (next, event) => {
|
|
120
|
+
if (!isMouse(event))
|
|
121
|
+
return;
|
|
122
|
+
release();
|
|
123
|
+
target = next;
|
|
124
|
+
if (store.getSnapshot().value === next.value)
|
|
125
|
+
return;
|
|
126
|
+
// Already open, or still fading out: switch at once rather than waiting to rest.
|
|
127
|
+
if (owner !== null || onScreen()) {
|
|
128
|
+
return open(next.value, event.nativeEvent, event.currentTarget);
|
|
129
|
+
}
|
|
130
|
+
openAfterRest(event.nativeEvent, event.currentTarget);
|
|
131
|
+
},
|
|
132
|
+
/** Moving restarts the wait, so the tab opens once the mouse comes to rest. */
|
|
133
|
+
moveOnTab: (event) => {
|
|
134
|
+
if (!isMouse(event) || openTimer === undefined)
|
|
135
|
+
return;
|
|
136
|
+
openAfterRest(event.nativeEvent, event.currentTarget);
|
|
137
|
+
},
|
|
138
|
+
leaveTab: (event) => {
|
|
139
|
+
if (!isMouse(event))
|
|
140
|
+
return;
|
|
141
|
+
release();
|
|
142
|
+
if (owner === null)
|
|
143
|
+
return;
|
|
144
|
+
// Onto another hover tab, whose arrival hands over, or straight into the popup.
|
|
145
|
+
if (isInside(tabs, event.relatedTarget))
|
|
146
|
+
return;
|
|
147
|
+
const popup = store.popupRef.current;
|
|
148
|
+
if (popup && isInside([popup], event.relatedTarget))
|
|
149
|
+
return;
|
|
150
|
+
armCone(event.currentTarget, event.nativeEvent);
|
|
151
|
+
},
|
|
152
|
+
enterPopup: (event) => {
|
|
153
|
+
if (!isMouse(event))
|
|
154
|
+
return;
|
|
155
|
+
stopCone();
|
|
156
|
+
clearTimeout(closeTimer);
|
|
157
|
+
},
|
|
158
|
+
leavePopup: (event) => {
|
|
159
|
+
if (!isMouse(event) || owner === null || isInside(tabs, event.relatedTarget))
|
|
160
|
+
return;
|
|
161
|
+
closeAfterDelay(event.nativeEvent);
|
|
162
|
+
},
|
|
163
|
+
/** A press on the tab, or a press or focus inside the popup: it is in use now. */
|
|
164
|
+
claim: () => {
|
|
165
|
+
release();
|
|
166
|
+
owner = null;
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { TabsAlign, TabsPopupProps, TabsPopupState, TabsPortalProps, TabsPositionerProps, TabsPositionerState, TabsSide, } from "./anchored.js";
|
|
2
|
+
export * as Tabs from "./index.parts.js";
|
|
3
|
+
export type { TabsDirection, TabsRootChangeEventDetails, TabsRootChangeEventReason, TabsSelectOnClose, TabsState, TabsStore, } from "./store.js";
|
|
4
|
+
export { useTabs, useTabsStore } from "./store.js";
|
|
5
|
+
export type { TabsActionProps, TabsCloseProps, TabsIconProps, TabsListProps, TabsOrientation, TabsPartState, TabsRootProps, TabsTriggerProps, TabsTriggerState, TabsViewportProps, TabsViewportState, } from "./tabs.js";
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { TabsPopup as Popup, TabsPortal as Portal, TabsPositioner as Positioner, } from "./anchored.js";
|
|
2
|
+
/** Create a standalone store handle: `<Tabs.Root store={…}>` plus `useTabsStore(store, selector)` for reading it from outside the tree. */
|
|
3
|
+
export { createTabsStore as createStore } from "./store.js";
|
|
4
|
+
export { TabsAction as Action, TabsClose as Close, TabsIcon as Icon, TabsList as List, TabsRoot as Root, TabsTrigger as Trigger, TabsViewport as Viewport, } from "./tabs.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Part names for the `Tabs` namespace. Deliberately carries no "use client"
|
|
2
|
+
// directive: this module and index.ts must stay server-resolvable so a React
|
|
3
|
+
// Server Component can reach Tabs.Root and its siblings through them. Each
|
|
4
|
+
// part module carries the directive itself.
|
|
5
|
+
export { TabsPopup as Popup, TabsPortal as Portal, TabsPositioner as Positioner, } from "./anchored.js";
|
|
6
|
+
/** Create a standalone store handle: `<Tabs.Root store={…}>` plus `useTabsStore(store, selector)` for reading it from outside the tree. */
|
|
7
|
+
export { createTabsStore as createStore } from "./store.js";
|
|
8
|
+
export { TabsAction as Action, TabsClose as Close, TabsIcon as Icon, TabsList as List, TabsRoot as Root, TabsTrigger as Trigger, TabsViewport as Viewport, } from "./tabs.js";
|