@godxjp/ui 20.2.1 → 22.0.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/dist/app/app-provider.js +2 -1
- package/dist/components/data-display/badge.d.ts +28 -1
- package/dist/components/data-display/badge.js +2 -0
- package/dist/components/data-display/chat-bubble.d.ts +30 -0
- package/dist/components/data-display/chat-bubble.js +229 -0
- package/dist/components/data-display/data-table.d.ts +18 -2
- package/dist/components/data-display/data-table.js +4 -0
- package/dist/components/data-display/descriptions.js +4 -1
- package/dist/components/data-display/index.d.ts +5 -3
- package/dist/components/data-display/index.js +5 -2
- package/dist/components/data-display/popover.d.ts +19 -1
- package/dist/components/data-display/popover.js +12 -3
- package/dist/components/data-display/progress.d.ts +37 -0
- package/dist/components/data-display/progress.js +57 -5
- package/dist/components/data-display/scroll-area.js +26 -1
- package/dist/components/data-display/tree.d.ts +8 -0
- package/dist/components/data-display/tree.js +426 -0
- package/dist/components/data-entry/chat-composer.d.ts +50 -0
- package/dist/components/data-entry/chat-composer.js +163 -0
- package/dist/components/data-entry/chat-suggestion.d.ts +28 -0
- package/dist/components/data-entry/chat-suggestion.js +285 -0
- package/dist/components/data-entry/date-picker.d.ts +10 -3
- package/dist/components/data-entry/date-picker.js +503 -238
- package/dist/components/data-entry/form-field.d.ts +1 -1
- package/dist/components/data-entry/form-field.js +4 -1
- package/dist/components/data-entry/form.js +10 -1
- package/dist/components/data-entry/index.d.ts +4 -6
- package/dist/components/data-entry/index.js +4 -6
- package/dist/components/data-entry/label.d.ts +14 -1
- package/dist/components/data-entry/label.js +6 -1
- package/dist/components/data-entry/radio.d.ts +27 -6
- package/dist/components/data-entry/radio.js +88 -43
- package/dist/components/data-entry/search-select.js +8 -1
- package/dist/components/data-entry/select.js +2 -0
- package/dist/components/data-entry/switch.d.ts +6 -3
- package/dist/components/data-entry/switch.js +34 -11
- package/dist/components/data-entry/textarea.js +3 -1
- package/dist/components/data-entry/tree-utils.d.ts +10 -48
- package/dist/components/data-entry/tree-utils.js +1 -154
- package/dist/components/layout/mobile-shell.d.ts +1 -1
- package/dist/components/layout/mobile-shell.js +2 -0
- package/dist/components/navigation/menubar.d.ts +10 -3
- package/dist/components/navigation/steps.d.ts +1 -1
- package/dist/components/navigation/steps.js +3 -1
- package/dist/components/navigation/tabs-scroll.d.ts +27 -0
- package/dist/components/navigation/tabs-scroll.js +52 -1
- package/dist/components/navigation/tabs.d.ts +2 -2
- package/dist/components/navigation/tabs.js +88 -23
- package/dist/components/ui/index.d.ts +0 -1
- package/dist/components/ui/index.js +0 -1
- package/dist/components/ui/segmented.d.ts +15 -1
- package/dist/components/ui/segmented.js +13 -6
- package/dist/i18n/messages/en.json +48 -2
- package/dist/i18n/messages/ja.json +46 -2
- package/dist/i18n/messages/vi.json +47 -2
- package/dist/lib/datetime/picker-format.d.ts +18 -0
- package/dist/lib/datetime/picker-format.js +35 -1
- package/dist/lib/field-a11y.d.ts +12 -0
- package/dist/lib/field-a11y.js +14 -1
- package/dist/lib/tree.d.ts +53 -0
- package/dist/lib/tree.js +155 -0
- package/dist/props/components/data-display.prop.d.ts +194 -1
- package/dist/props/components/data-entry.prop.d.ts +307 -99
- package/dist/props/components/index.d.ts +1 -1
- package/dist/props/components/layout.prop.d.ts +12 -1
- package/dist/props/components/navigation.prop.d.ts +76 -1
- package/dist/props/registry.d.ts +156 -19
- package/dist/props/registry.js +218 -18
- package/dist/props/vocabulary/data.prop.d.ts +12 -0
- package/dist/props/vocabulary/index.d.ts +2 -2
- package/dist/props/vocabulary/layout.prop.d.ts +11 -0
- package/dist/styles/badge-layout.css +4 -0
- package/dist/styles/card-layout.css +6 -6
- package/dist/styles/control.css +22 -6
- package/dist/styles/data-display-layout.css +385 -68
- package/dist/styles/data-entry-layout.css +64 -0
- package/dist/styles/focus-ring.css +4 -2
- package/dist/styles/navigation-layout.css +25 -0
- package/dist/styles/shell-layout.css +7 -1
- package/dist/styles/table-layout.css +34 -0
- package/dist/tokens/base.css +3 -0
- package/dist/tokens/components/chat-bubble.css +36 -0
- package/dist/tokens/components/chat-composer.css +19 -0
- package/dist/tokens/components/data-display.css +9 -6
- package/dist/tokens/components/descriptions.css +4 -0
- package/dist/tokens/components/navigation.css +6 -0
- package/dist/tokens/components/segmented.css +0 -2
- package/dist/tokens/components/shell.css +3 -0
- package/dist/tokens/components/table.css +3 -0
- package/dist/tokens/components/tree.css +27 -0
- package/dist/tokens/foundation.css +7 -0
- package/docs/COMPONENTS.md +14 -14
- package/docs/CONSUMER-RULES.md +24 -1
- package/docs/FRAME-COVERAGE-LEDGER.md +1 -1
- package/docs/FRAME-COVERAGE-REPORT.md +9 -7
- package/docs/TOKENS.md +65 -0
- package/docs/data-display/badge.tsx +37 -0
- package/docs/data-display/chat-bubble.tsx +397 -0
- package/docs/data-display/data-table/index.tsx +18 -0
- package/docs/data-display/popover.tsx +41 -1
- package/docs/data-display/progress.tsx +28 -0
- package/docs/data-display/timeline.tsx +46 -0
- package/docs/data-display/tree.tsx +394 -0
- package/docs/data-entry/calendar.tsx +2 -2
- package/docs/data-entry/chat-composer.tsx +464 -0
- package/docs/data-entry/chat-suggestion.tsx +301 -0
- package/docs/data-entry/date-picker.tsx +143 -3
- package/docs/data-entry/form/examples/invoice-form.tsx +2 -2
- package/docs/data-entry/form-field/examples/a11y-contract.tsx +3 -5
- package/docs/data-entry/form.tsx +4 -4
- package/docs/data-entry/number-input.tsx +31 -0
- package/docs/data-entry/segmented.tsx +103 -1
- package/docs/layout/mobile-shell.tsx +5 -0
- package/docs/navigation/tabs.tsx +149 -0
- package/docs/roadmap/ai-chat-components.md +207 -0
- package/docs/roadmap/antd-parity.md +155 -0
- package/docs/roadmap/badge-tag-chip-count.md +172 -0
- package/docs/roadmap/list-masonry.md +159 -0
- package/docs/roadmap/parity-audit-data-display-feedback.md +567 -0
- package/docs/roadmap/parity-audit-data-entry.md +357 -0
- package/docs/roadmap/parity-audit-layout-navigation-general.md +464 -0
- package/docs/roadmap/parity-backlog.md +78 -0
- package/docs/roadmap/tree-components.md +151 -0
- package/docs/showcase/acme-portal.tsx +7 -0
- package/docs/showcase/table-tree-rows.tsx +4 -4
- package/package.json +13 -23
- package/scripts/ui-audit.mjs +17 -4
- package/dist/components/data-display/tree-list.d.ts +0 -13
- package/dist/components/data-display/tree-list.js +0 -26
- package/dist/components/data-entry/date-range-picker.d.ts +0 -11
- package/dist/components/data-entry/date-range-picker.js +0 -349
- package/dist/components/data-entry/month-picker.d.ts +0 -10
- package/dist/components/data-entry/month-picker.js +0 -241
- package/dist/components/data-entry/month-range-picker.d.ts +0 -10
- package/dist/components/data-entry/month-range-picker.js +0 -301
- package/dist/components/ui/date-range-picker.d.ts +0 -1
- package/dist/components/ui/date-range-picker.js +0 -2
- package/docs/data-display/tree-list.tsx +0 -107
- package/docs/data-entry/date-range-picker.tsx +0 -175
- package/docs/data-entry/month-picker.tsx +0 -101
- package/docs/data-entry/month-range-picker.tsx +0 -115
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { useTranslation } from "../../i18n/use-translation.js";
|
|
5
5
|
import { cn } from "../../lib/utils.js";
|
|
6
|
+
const RING_BOX = 40;
|
|
7
|
+
const RING_CENTRE = RING_BOX / 2;
|
|
8
|
+
const RING_RADIUS = 16;
|
|
9
|
+
const RING_ROTATION = `rotate(-90 ${RING_CENTRE} ${RING_CENTRE})`;
|
|
6
10
|
function Progress(props) {
|
|
7
11
|
const { t } = useTranslation();
|
|
8
12
|
const labelId = React.useId();
|
|
@@ -12,6 +16,8 @@ function Progress(props) {
|
|
|
12
16
|
segments,
|
|
13
17
|
value,
|
|
14
18
|
tone,
|
|
19
|
+
size,
|
|
20
|
+
shape,
|
|
15
21
|
over = false,
|
|
16
22
|
"aria-label": ariaLabel,
|
|
17
23
|
"aria-labelledby": ariaLabelledBy,
|
|
@@ -30,6 +36,7 @@ function Progress(props) {
|
|
|
30
36
|
{
|
|
31
37
|
className: cn("ui-progress", className),
|
|
32
38
|
"data-breakdown": "",
|
|
39
|
+
"data-size": size === "sm" ? "sm" : void 0,
|
|
33
40
|
role: "img",
|
|
34
41
|
"aria-labelledby": ariaLabelledBy,
|
|
35
42
|
"aria-label": ariaLabelledBy !== void 0 ? void 0 : ariaLabel !== void 0 ? `${ariaLabel}${separator}${spoken}` : label !== void 0 ? `${label}${separator}${spoken}` : spoken,
|
|
@@ -53,12 +60,14 @@ function Progress(props) {
|
|
|
53
60
|
const isOver = over && meterValue > 100;
|
|
54
61
|
const boundedValue = Math.max(0, Math.min(100, meterValue));
|
|
55
62
|
const effectiveTone = tone ?? (isOver ? "destructive" : "success");
|
|
56
|
-
return /* @__PURE__ */
|
|
63
|
+
return /* @__PURE__ */ jsx(
|
|
57
64
|
"div",
|
|
58
65
|
{
|
|
59
66
|
className: cn("ui-progress", className),
|
|
60
67
|
"data-tone": effectiveTone,
|
|
61
|
-
"data-
|
|
68
|
+
"data-size": size === "sm" ? "sm" : void 0,
|
|
69
|
+
"data-shape": shape === "ring" ? "ring" : void 0,
|
|
70
|
+
"data-over": isOver && shape !== "ring" ? "" : void 0,
|
|
62
71
|
role: "progressbar",
|
|
63
72
|
"aria-valuenow": boundedValue,
|
|
64
73
|
"aria-valuemin": 0,
|
|
@@ -67,10 +76,53 @@ function Progress(props) {
|
|
|
67
76
|
"aria-labelledby": labelledBy,
|
|
68
77
|
"aria-label": labelledBy !== void 0 ? void 0 : named ? ariaLabel : t("dataDisplay.progress.ariaLabel"),
|
|
69
78
|
...domProps,
|
|
70
|
-
children:
|
|
79
|
+
children: shape === "ring" ? (
|
|
80
|
+
/*
|
|
81
|
+
* The ARC is aria-hidden and the ring carries no text of its own for a screen reader: the
|
|
82
|
+
* element above is already `role="progressbar"` with `aria-valuenow`/`aria-valuetext`, so
|
|
83
|
+
* announcing the drawing as well would say the same number twice. `focusable="false"`
|
|
84
|
+
* because IE-era SVG semantics still reach some AT through the tab order.
|
|
85
|
+
*/
|
|
86
|
+
/* @__PURE__ */ jsxs("div", { className: "ui-progress-ring", children: [
|
|
87
|
+
/* @__PURE__ */ jsxs(
|
|
88
|
+
"svg",
|
|
89
|
+
{
|
|
90
|
+
className: "ui-progress-ring-svg",
|
|
91
|
+
viewBox: `0 0 ${RING_BOX} ${RING_BOX}`,
|
|
92
|
+
"aria-hidden": "true",
|
|
93
|
+
focusable: "false",
|
|
94
|
+
children: [
|
|
95
|
+
/* @__PURE__ */ jsx(
|
|
96
|
+
"circle",
|
|
97
|
+
{
|
|
98
|
+
className: "ui-progress-ring-track",
|
|
99
|
+
cx: RING_CENTRE,
|
|
100
|
+
cy: RING_CENTRE,
|
|
101
|
+
r: RING_RADIUS,
|
|
102
|
+
pathLength: 100
|
|
103
|
+
}
|
|
104
|
+
),
|
|
105
|
+
/* @__PURE__ */ jsx(
|
|
106
|
+
"circle",
|
|
107
|
+
{
|
|
108
|
+
className: "ui-progress-ring-indicator",
|
|
109
|
+
cx: RING_CENTRE,
|
|
110
|
+
cy: RING_CENTRE,
|
|
111
|
+
r: RING_RADIUS,
|
|
112
|
+
pathLength: 100,
|
|
113
|
+
transform: RING_ROTATION,
|
|
114
|
+
strokeDasharray: `${boundedValue} 100`
|
|
115
|
+
}
|
|
116
|
+
)
|
|
117
|
+
]
|
|
118
|
+
}
|
|
119
|
+
),
|
|
120
|
+
label !== void 0 ? /* @__PURE__ */ jsx("div", { className: "ui-progress-ring-label", id: labelId, children: label }) : null
|
|
121
|
+
] })
|
|
122
|
+
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
71
123
|
/* @__PURE__ */ jsx("div", { className: "ui-progress-track", children: /* @__PURE__ */ jsx("div", { className: "ui-progress-bar", style: { width: `${boundedValue}%` } }) }),
|
|
72
124
|
caption
|
|
73
|
-
]
|
|
125
|
+
] })
|
|
74
126
|
}
|
|
75
127
|
);
|
|
76
128
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useLayoutEffect } from "@react-aria/utils";
|
|
3
4
|
import * as React from "react";
|
|
4
5
|
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
|
|
5
6
|
import { cn } from "../../lib/utils.js";
|
|
@@ -112,6 +113,18 @@ function useBottomAnchor(viewport, enabled, offset, onAnchoredChange) {
|
|
|
112
113
|
};
|
|
113
114
|
}, [viewport, enabled, offset]);
|
|
114
115
|
}
|
|
116
|
+
function useAmbientDirection(root, provided) {
|
|
117
|
+
const [ambient, setAmbient] = React.useState(void 0);
|
|
118
|
+
useLayoutEffect(() => {
|
|
119
|
+
if (provided !== void 0) return;
|
|
120
|
+
const parent = root?.parentElement;
|
|
121
|
+
if (!parent || typeof window === "undefined" || typeof window.getComputedStyle !== "function") {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
setAmbient(window.getComputedStyle(parent).direction === "rtl" ? "rtl" : "ltr");
|
|
125
|
+
}, [root, provided]);
|
|
126
|
+
return provided ?? ambient;
|
|
127
|
+
}
|
|
115
128
|
const ScrollArea = React.forwardRef(
|
|
116
129
|
({
|
|
117
130
|
className,
|
|
@@ -121,9 +134,20 @@ const ScrollArea = React.forwardRef(
|
|
|
121
134
|
anchorOffset,
|
|
122
135
|
onAnchoredChange,
|
|
123
136
|
orientation = "vertical",
|
|
137
|
+
dir,
|
|
124
138
|
...props
|
|
125
139
|
}, ref) => {
|
|
126
140
|
const [viewport, setViewport] = React.useState(null);
|
|
141
|
+
const [root, setRoot] = React.useState(null);
|
|
142
|
+
const attachRoot = React.useCallback(
|
|
143
|
+
(node) => {
|
|
144
|
+
setRoot(node);
|
|
145
|
+
if (typeof ref === "function") ref(node);
|
|
146
|
+
else if (ref) ref.current = node;
|
|
147
|
+
},
|
|
148
|
+
[ref]
|
|
149
|
+
);
|
|
150
|
+
const direction = useAmbientDirection(root, dir);
|
|
127
151
|
const attachViewport = React.useCallback(
|
|
128
152
|
(node) => {
|
|
129
153
|
setViewport(node);
|
|
@@ -137,7 +161,8 @@ const ScrollArea = React.forwardRef(
|
|
|
137
161
|
return /* @__PURE__ */ jsxs(
|
|
138
162
|
ScrollAreaPrimitive.Root,
|
|
139
163
|
{
|
|
140
|
-
ref,
|
|
164
|
+
ref: attachRoot,
|
|
165
|
+
dir: direction,
|
|
141
166
|
className: cn("relative overflow-hidden", className),
|
|
142
167
|
...props,
|
|
143
168
|
children: [
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { TreeProp } from "../../props/components/data-display.prop.js";
|
|
3
|
+
export type { TreeNodeProp, TreeProp, TreeProp as TreeProps, } from "../../props/components/data-display.prop.js";
|
|
4
|
+
/**
|
|
5
|
+
* `ref` reaches the `role="tree"` container — the element a page scrolls to, measures, or
|
|
6
|
+
* focus-manages. Every unknown prop (`aria-*`, `data-*`) rides `...ariaProps` onto the same node.
|
|
7
|
+
*/
|
|
8
|
+
export declare const Tree: React.ForwardRefExoticComponent<TreeProp & React.RefAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { ChevronDown, ChevronRight, File as FileIcon, Folder, FolderOpen } from "lucide-react";
|
|
5
|
+
import { useTranslation } from "../../i18n/use-translation.js";
|
|
6
|
+
import { cn } from "../../lib/utils.js";
|
|
7
|
+
import { CheckboxVisual } from "../data-entry/checkbox.js";
|
|
8
|
+
import { Skeleton } from "../feedback/skeleton.js";
|
|
9
|
+
import {
|
|
10
|
+
collectAllExpandableKeys,
|
|
11
|
+
flattenVisibleTree,
|
|
12
|
+
getDescendantValues,
|
|
13
|
+
normalizeTreeOptions,
|
|
14
|
+
reactNodeText
|
|
15
|
+
} from "../../lib/tree.js";
|
|
16
|
+
function toArray(value) {
|
|
17
|
+
if (value == null) return [];
|
|
18
|
+
return Array.isArray(value) ? value : [value];
|
|
19
|
+
}
|
|
20
|
+
function isTickable(node) {
|
|
21
|
+
return !node.disabled && !node.disableCheckbox;
|
|
22
|
+
}
|
|
23
|
+
function normalizeCheckedValues(nodes, source, out = /* @__PURE__ */ new Set()) {
|
|
24
|
+
for (const node of nodes) {
|
|
25
|
+
const children = node.children ?? [];
|
|
26
|
+
if (children.length === 0) {
|
|
27
|
+
if (source.has(node.value)) out.add(node.value);
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
normalizeCheckedValues(children, source, out);
|
|
31
|
+
const tickable = children.filter(isTickable);
|
|
32
|
+
const allOn = tickable.length > 0 && tickable.every((child) => out.has(child.value));
|
|
33
|
+
if (allOn || tickable.length === 0 && source.has(node.value)) out.add(node.value);
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
function indexTree(nodes, into = /* @__PURE__ */ new Map()) {
|
|
38
|
+
for (const node of nodes) {
|
|
39
|
+
into.set(node.value, node);
|
|
40
|
+
if (node.children?.length) indexTree(node.children, into);
|
|
41
|
+
}
|
|
42
|
+
return into;
|
|
43
|
+
}
|
|
44
|
+
function isRtl(element) {
|
|
45
|
+
return element.closest("[dir]")?.getAttribute("dir")?.toLowerCase() === "rtl";
|
|
46
|
+
}
|
|
47
|
+
function TreeRoot({
|
|
48
|
+
treeData,
|
|
49
|
+
fieldNames,
|
|
50
|
+
value,
|
|
51
|
+
defaultValue,
|
|
52
|
+
onValueChange,
|
|
53
|
+
multiple = false,
|
|
54
|
+
checkable: checkableProp = false,
|
|
55
|
+
checkStrictly = false,
|
|
56
|
+
checkedValues,
|
|
57
|
+
defaultCheckedValues,
|
|
58
|
+
onCheckedValuesChange,
|
|
59
|
+
expandedValues,
|
|
60
|
+
defaultExpandedValues,
|
|
61
|
+
onExpandedValuesChange,
|
|
62
|
+
defaultExpandAll = false,
|
|
63
|
+
loadData,
|
|
64
|
+
titleRender,
|
|
65
|
+
showLine = false,
|
|
66
|
+
showIcon = false,
|
|
67
|
+
variant = "default",
|
|
68
|
+
size = "md",
|
|
69
|
+
disabled = false,
|
|
70
|
+
className,
|
|
71
|
+
id,
|
|
72
|
+
forwardedRef,
|
|
73
|
+
...ariaProps
|
|
74
|
+
}) {
|
|
75
|
+
const { t } = useTranslation();
|
|
76
|
+
const reactId = React.useId();
|
|
77
|
+
const treeId = id ?? `${reactId}-tree`;
|
|
78
|
+
const options = React.useMemo(
|
|
79
|
+
() => normalizeTreeOptions(treeData, fieldNames),
|
|
80
|
+
[treeData, fieldNames]
|
|
81
|
+
);
|
|
82
|
+
const nodeIndex = React.useMemo(() => indexTree(options), [options]);
|
|
83
|
+
const [internalExpanded, setInternalExpanded] = React.useState(() => {
|
|
84
|
+
if (defaultExpandedValues) return [...defaultExpandedValues];
|
|
85
|
+
return defaultExpandAll ? collectAllExpandableKeys(options) : [];
|
|
86
|
+
});
|
|
87
|
+
const isExpandedControlled = expandedValues !== void 0;
|
|
88
|
+
const expanded = isExpandedControlled ? [...expandedValues] : internalExpanded;
|
|
89
|
+
const expandedSet = new Set(expanded);
|
|
90
|
+
const commitExpanded = (next) => {
|
|
91
|
+
if (!isExpandedControlled) setInternalExpanded(next);
|
|
92
|
+
onExpandedValuesChange?.(next);
|
|
93
|
+
};
|
|
94
|
+
const isValueControlled = value !== void 0;
|
|
95
|
+
const [internalValue, setInternalValue] = React.useState(() => toArray(defaultValue));
|
|
96
|
+
const selected = isValueControlled ? toArray(value) : internalValue;
|
|
97
|
+
const commitValue = (next) => {
|
|
98
|
+
if (!isValueControlled) setInternalValue(next);
|
|
99
|
+
onValueChange?.(multiple ? next : next[0]);
|
|
100
|
+
};
|
|
101
|
+
const isCheckedControlled = checkedValues !== void 0;
|
|
102
|
+
const [internalChecked, setInternalChecked] = React.useState(
|
|
103
|
+
() => defaultCheckedValues ? [...defaultCheckedValues] : []
|
|
104
|
+
);
|
|
105
|
+
const checked = isCheckedControlled ? [...checkedValues] : internalChecked;
|
|
106
|
+
const checkedSet = checkStrictly ? new Set(checked) : normalizeCheckedValues(options, new Set(checked));
|
|
107
|
+
const commitChecked = (next) => {
|
|
108
|
+
const settled = checkStrictly ? next : [...normalizeCheckedValues(options, new Set(next))];
|
|
109
|
+
if (!isCheckedControlled) setInternalChecked(settled);
|
|
110
|
+
onCheckedValuesChange?.(settled);
|
|
111
|
+
};
|
|
112
|
+
const requestedLoads = React.useRef(/* @__PURE__ */ new Set());
|
|
113
|
+
const [loadingValues, setLoadingValues] = React.useState(() => /* @__PURE__ */ new Set());
|
|
114
|
+
const requestLoad = (node) => {
|
|
115
|
+
if (!loadData) return;
|
|
116
|
+
if ((node.children?.length ?? 0) > 0 || node.isLeaf === true) return;
|
|
117
|
+
if (requestedLoads.current.has(node.value)) return;
|
|
118
|
+
requestedLoads.current.add(node.value);
|
|
119
|
+
setLoadingValues((prev) => new Set(prev).add(node.value));
|
|
120
|
+
void Promise.resolve(loadData(node)).finally(() => {
|
|
121
|
+
setLoadingValues((prev) => {
|
|
122
|
+
const next = new Set(prev);
|
|
123
|
+
next.delete(node.value);
|
|
124
|
+
return next;
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
};
|
|
128
|
+
const visible = flattenVisibleTree(options, expandedSet);
|
|
129
|
+
const visibleIndex = new Map(visible.map((entry, index) => [entry.node.value, index]));
|
|
130
|
+
const [activeValue, setActiveValue] = React.useState(null);
|
|
131
|
+
const rovingValue = (activeValue && visibleIndex.has(activeValue) ? activeValue : null) ?? visible.find((entry) => selected.includes(entry.node.value))?.node.value ?? visible[0]?.node.value ?? null;
|
|
132
|
+
const nodeRefs = React.useRef(/* @__PURE__ */ new Map());
|
|
133
|
+
const pendingFocus = React.useRef(null);
|
|
134
|
+
React.useEffect(() => {
|
|
135
|
+
const target = pendingFocus.current;
|
|
136
|
+
if (!target) return;
|
|
137
|
+
pendingFocus.current = null;
|
|
138
|
+
nodeRefs.current.get(target)?.focus();
|
|
139
|
+
});
|
|
140
|
+
const moveTo = (index) => {
|
|
141
|
+
const entry = visible[index];
|
|
142
|
+
if (!entry) return;
|
|
143
|
+
setActiveValue(entry.node.value);
|
|
144
|
+
pendingFocus.current = entry.node.value;
|
|
145
|
+
};
|
|
146
|
+
const expandableOf = (node) => (node.children?.length ?? 0) > 0 && node.isLeaf !== true || Boolean(loadData && node.isLeaf === false && !node.children?.length);
|
|
147
|
+
const expandNode = (node) => {
|
|
148
|
+
if (disabled || expandedSet.has(node.value)) return;
|
|
149
|
+
requestLoad(node);
|
|
150
|
+
commitExpanded([...expanded, node.value]);
|
|
151
|
+
};
|
|
152
|
+
const collapseNode = (node) => {
|
|
153
|
+
if (disabled) return;
|
|
154
|
+
commitExpanded(expanded.filter((entry) => entry !== node.value));
|
|
155
|
+
};
|
|
156
|
+
const toggleExpand = (node) => {
|
|
157
|
+
if (expandedSet.has(node.value)) collapseNode(node);
|
|
158
|
+
else expandNode(node);
|
|
159
|
+
};
|
|
160
|
+
const checkStateOf = (node) => {
|
|
161
|
+
const children = node.children ?? [];
|
|
162
|
+
if (checkStrictly || children.length === 0) {
|
|
163
|
+
return checkedSet.has(node.value) ? "checked" : "unchecked";
|
|
164
|
+
}
|
|
165
|
+
if (checkedSet.has(node.value)) return "checked";
|
|
166
|
+
const descendants = getDescendantValues(node).slice(1);
|
|
167
|
+
return descendants.some((entry) => checkedSet.has(entry)) ? "indeterminate" : "unchecked";
|
|
168
|
+
};
|
|
169
|
+
const toggleCheck = (node) => {
|
|
170
|
+
if (disabled || node.disabled || node.disableCheckbox) return;
|
|
171
|
+
const isOn = checkStateOf(node) === "checked";
|
|
172
|
+
if (checkStrictly) {
|
|
173
|
+
commitChecked(
|
|
174
|
+
isOn ? checked.filter((entry) => entry !== node.value) : [...checked, node.value]
|
|
175
|
+
);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const related = getDescendantValues(node).filter((entry) => {
|
|
179
|
+
const target = nodeIndex.get(entry);
|
|
180
|
+
return target ? isTickable(target) : false;
|
|
181
|
+
});
|
|
182
|
+
commitChecked(
|
|
183
|
+
isOn ? checked.filter((entry) => !related.includes(entry)) : [.../* @__PURE__ */ new Set([...checked, ...related])]
|
|
184
|
+
);
|
|
185
|
+
};
|
|
186
|
+
const select = (node) => {
|
|
187
|
+
if (disabled || node.disabled) return;
|
|
188
|
+
const isOn = selected.includes(node.value);
|
|
189
|
+
if (multiple) {
|
|
190
|
+
commitValue(
|
|
191
|
+
isOn ? selected.filter((entry) => entry !== node.value) : [...selected, node.value]
|
|
192
|
+
);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
commitValue(isOn ? [] : [node.value]);
|
|
196
|
+
};
|
|
197
|
+
const activate = (node) => {
|
|
198
|
+
if (checkableProp) toggleCheck(node);
|
|
199
|
+
else select(node);
|
|
200
|
+
};
|
|
201
|
+
const typeAhead = React.useRef({ buffer: "", at: 0 });
|
|
202
|
+
const jumpByLabel = (key, from) => {
|
|
203
|
+
const now = Date.now();
|
|
204
|
+
const state = typeAhead.current;
|
|
205
|
+
state.buffer = now - state.at > 800 ? key : state.buffer + key;
|
|
206
|
+
state.at = now;
|
|
207
|
+
const needle = state.buffer.toLowerCase();
|
|
208
|
+
for (let step = 1; step <= visible.length; step += 1) {
|
|
209
|
+
const index = (from + step) % visible.length;
|
|
210
|
+
if (reactNodeText(visible[index].node.label).toLowerCase().startsWith(needle)) {
|
|
211
|
+
moveTo(index);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
const siblingIndexes = (index, depth) => {
|
|
217
|
+
const out = [];
|
|
218
|
+
for (let i = index; i >= 0; i -= 1) {
|
|
219
|
+
if (visible[i].depth < depth) break;
|
|
220
|
+
if (visible[i].depth === depth) out.unshift(i);
|
|
221
|
+
}
|
|
222
|
+
for (let i = index + 1; i < visible.length; i += 1) {
|
|
223
|
+
if (visible[i].depth < depth) break;
|
|
224
|
+
if (visible[i].depth === depth) out.push(i);
|
|
225
|
+
}
|
|
226
|
+
return out;
|
|
227
|
+
};
|
|
228
|
+
const onNodeKeyDown = (event, node) => {
|
|
229
|
+
const index = visibleIndex.get(node.value);
|
|
230
|
+
if (index === void 0) return;
|
|
231
|
+
const depth = visible[index].depth;
|
|
232
|
+
const expandable = expandableOf(node);
|
|
233
|
+
const isOpen = expandable && expandedSet.has(node.value);
|
|
234
|
+
const rtl = isRtl(event.currentTarget);
|
|
235
|
+
const inward = rtl ? "ArrowLeft" : "ArrowRight";
|
|
236
|
+
const outward = rtl ? "ArrowRight" : "ArrowLeft";
|
|
237
|
+
if (event.key === "ArrowDown") {
|
|
238
|
+
event.preventDefault();
|
|
239
|
+
moveTo(index + 1);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (event.key === "ArrowUp") {
|
|
243
|
+
event.preventDefault();
|
|
244
|
+
moveTo(index - 1);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (event.key === "Home") {
|
|
248
|
+
event.preventDefault();
|
|
249
|
+
moveTo(0);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (event.key === "End") {
|
|
253
|
+
event.preventDefault();
|
|
254
|
+
moveTo(visible.length - 1);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
if (event.key === inward) {
|
|
258
|
+
event.preventDefault();
|
|
259
|
+
if (expandable && !isOpen) expandNode(node);
|
|
260
|
+
else if (isOpen) moveTo(index + 1);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (event.key === outward) {
|
|
264
|
+
event.preventDefault();
|
|
265
|
+
if (isOpen) {
|
|
266
|
+
collapseNode(node);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
for (let i = index - 1; i >= 0; i -= 1) {
|
|
270
|
+
if (visible[i].depth < depth) {
|
|
271
|
+
moveTo(i);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
278
|
+
event.preventDefault();
|
|
279
|
+
activate(node);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (event.key === "*") {
|
|
283
|
+
event.preventDefault();
|
|
284
|
+
const siblings = siblingIndexes(index, depth).map((i) => visible[i].node).filter((sibling) => expandableOf(sibling));
|
|
285
|
+
siblings.forEach(requestLoad);
|
|
286
|
+
commitExpanded([.../* @__PURE__ */ new Set([...expanded, ...siblings.map((sibling) => sibling.value)])]);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (event.key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey) {
|
|
290
|
+
jumpByLabel(event.key, index);
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
const renderNodes = (nodes, depth) => nodes.flatMap((node, position) => {
|
|
294
|
+
const hasChildren = (node.children?.length ?? 0) > 0 && node.isLeaf !== true;
|
|
295
|
+
const expandable = expandableOf(node);
|
|
296
|
+
const isOpen = expandable && expandedSet.has(node.value);
|
|
297
|
+
const isLoading = loadingValues.has(node.value);
|
|
298
|
+
const isSelected = selected.includes(node.value);
|
|
299
|
+
const checkState = checkableProp ? checkStateOf(node) : "unchecked";
|
|
300
|
+
const nodeDisabled = disabled || Boolean(node.disabled);
|
|
301
|
+
const labelId = `${treeId}-${node.value}-label`;
|
|
302
|
+
const groupId = `${treeId}-${node.value}-group`;
|
|
303
|
+
const showGroup = isOpen && (hasChildren || isLoading);
|
|
304
|
+
const glyph = node.icon ?? (variant === "directory" ? hasChildren ? isOpen ? /* @__PURE__ */ jsx(FolderOpen, {}) : /* @__PURE__ */ jsx(Folder, {}) : /* @__PURE__ */ jsx(FileIcon, {}) : null);
|
|
305
|
+
const item = /* @__PURE__ */ jsxs(
|
|
306
|
+
"div",
|
|
307
|
+
{
|
|
308
|
+
ref: (element) => {
|
|
309
|
+
nodeRefs.current.set(node.value, element);
|
|
310
|
+
},
|
|
311
|
+
role: "treeitem",
|
|
312
|
+
"aria-labelledby": labelId,
|
|
313
|
+
"aria-level": depth + 1,
|
|
314
|
+
"aria-setsize": nodes.length,
|
|
315
|
+
"aria-posinset": position + 1,
|
|
316
|
+
"aria-selected": isSelected,
|
|
317
|
+
"aria-expanded": expandable ? isOpen : void 0,
|
|
318
|
+
"aria-owns": showGroup ? groupId : void 0,
|
|
319
|
+
"aria-checked": checkableProp ? checkState === "indeterminate" ? "mixed" : checkState === "checked" : void 0,
|
|
320
|
+
"aria-disabled": nodeDisabled || void 0,
|
|
321
|
+
"aria-busy": isLoading || void 0,
|
|
322
|
+
tabIndex: rovingValue === node.value ? 0 : -1,
|
|
323
|
+
onFocus: () => setActiveValue(node.value),
|
|
324
|
+
onKeyDown: (event) => onNodeKeyDown(event, node),
|
|
325
|
+
onClick: () => {
|
|
326
|
+
if (nodeDisabled) return;
|
|
327
|
+
select(node);
|
|
328
|
+
},
|
|
329
|
+
"data-selected": isSelected ? "true" : void 0,
|
|
330
|
+
"data-disabled": nodeDisabled ? "" : void 0,
|
|
331
|
+
className: "ui-tree-node ui-focus-ring",
|
|
332
|
+
style: { "--tree-node-level": depth },
|
|
333
|
+
children: [
|
|
334
|
+
/* @__PURE__ */ jsx(
|
|
335
|
+
"span",
|
|
336
|
+
{
|
|
337
|
+
"aria-hidden": "true",
|
|
338
|
+
"data-leaf": expandable ? void 0 : "",
|
|
339
|
+
className: "ui-tree-switcher",
|
|
340
|
+
title: expandable ? isOpen ? t("dataDisplay.tree.collapse") : t("dataDisplay.tree.expand") : void 0,
|
|
341
|
+
onClick: (event) => {
|
|
342
|
+
event.stopPropagation();
|
|
343
|
+
if (nodeDisabled || !expandable) return;
|
|
344
|
+
toggleExpand(node);
|
|
345
|
+
},
|
|
346
|
+
children: expandable ? isOpen ? /* @__PURE__ */ jsx(ChevronDown, {}) : /* @__PURE__ */ jsx(ChevronRight, {}) : null
|
|
347
|
+
}
|
|
348
|
+
),
|
|
349
|
+
checkableProp ? /* @__PURE__ */ jsx(
|
|
350
|
+
"span",
|
|
351
|
+
{
|
|
352
|
+
className: "ui-tree-check",
|
|
353
|
+
onClick: (event) => {
|
|
354
|
+
event.stopPropagation();
|
|
355
|
+
toggleCheck(node);
|
|
356
|
+
},
|
|
357
|
+
children: /* @__PURE__ */ jsx(
|
|
358
|
+
CheckboxVisual,
|
|
359
|
+
{
|
|
360
|
+
checked: checkState === "checked",
|
|
361
|
+
indeterminate: checkState === "indeterminate",
|
|
362
|
+
disabled: nodeDisabled || Boolean(node.disableCheckbox)
|
|
363
|
+
}
|
|
364
|
+
)
|
|
365
|
+
}
|
|
366
|
+
) : null,
|
|
367
|
+
showIcon && glyph ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "ui-tree-icon", children: glyph }) : null,
|
|
368
|
+
/* @__PURE__ */ jsx("span", { id: labelId, className: "ui-tree-label", children: titleRender ? titleRender(node) : node.label }),
|
|
369
|
+
isSelected ? /* @__PURE__ */ jsx("span", { className: "sr-only", children: t("dataDisplay.tree.selected") }) : null
|
|
370
|
+
]
|
|
371
|
+
},
|
|
372
|
+
node.value
|
|
373
|
+
);
|
|
374
|
+
if (!showGroup) return [item];
|
|
375
|
+
return [
|
|
376
|
+
item,
|
|
377
|
+
/* @__PURE__ */ jsx(
|
|
378
|
+
"div",
|
|
379
|
+
{
|
|
380
|
+
id: groupId,
|
|
381
|
+
role: "group",
|
|
382
|
+
className: "ui-tree-group",
|
|
383
|
+
style: { "--tree-node-level": depth + 1 },
|
|
384
|
+
children: hasChildren ? renderNodes(node.children, depth + 1) : /* @__PURE__ */ jsxs(
|
|
385
|
+
"div",
|
|
386
|
+
{
|
|
387
|
+
className: "ui-tree-loading",
|
|
388
|
+
style: { "--tree-node-level": depth + 1 },
|
|
389
|
+
children: [
|
|
390
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "ui-tree-loading-bar" }),
|
|
391
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: t("dataDisplay.tree.loading") })
|
|
392
|
+
]
|
|
393
|
+
}
|
|
394
|
+
)
|
|
395
|
+
},
|
|
396
|
+
`${node.value}::group`
|
|
397
|
+
)
|
|
398
|
+
];
|
|
399
|
+
});
|
|
400
|
+
const isEmpty = visible.length === 0;
|
|
401
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
402
|
+
/* @__PURE__ */ jsx(
|
|
403
|
+
"div",
|
|
404
|
+
{
|
|
405
|
+
...ariaProps,
|
|
406
|
+
ref: forwardedRef,
|
|
407
|
+
id: treeId,
|
|
408
|
+
role: "tree",
|
|
409
|
+
"aria-multiselectable": multiple || checkableProp,
|
|
410
|
+
"aria-disabled": disabled || void 0,
|
|
411
|
+
"data-size": size,
|
|
412
|
+
"data-variant": variant,
|
|
413
|
+
"data-show-line": showLine ? "true" : void 0,
|
|
414
|
+
"data-empty": isEmpty ? "true" : void 0,
|
|
415
|
+
className: cn("ui-tree", className),
|
|
416
|
+
children: isEmpty ? null : renderNodes(options, 0)
|
|
417
|
+
}
|
|
418
|
+
),
|
|
419
|
+
isEmpty ? /* @__PURE__ */ jsx("p", { role: "status", className: "ui-tree-empty", children: t("dataDisplay.tree.empty") }) : null
|
|
420
|
+
] });
|
|
421
|
+
}
|
|
422
|
+
const Tree = React.forwardRef((props, ref) => /* @__PURE__ */ jsx(TreeRoot, { ...props, forwardedRef: ref }));
|
|
423
|
+
Tree.displayName = "Tree";
|
|
424
|
+
export {
|
|
425
|
+
Tree
|
|
426
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export type { ChatComposerProp, ChatComposerProp as ChatComposerProps, ChatComposerSubmitTypeProp, } from "../../props/components/data-entry.prop.js";
|
|
3
|
+
/**
|
|
4
|
+
* ChatComposer — the message input of a conversation (Ant Design X `Sender`).
|
|
5
|
+
*
|
|
6
|
+
* ## Why a component and not a composition
|
|
7
|
+
*
|
|
8
|
+
* Three behaviours live here and nowhere else, and every app that hand-rolls a composer gets at
|
|
9
|
+
* least one of them wrong:
|
|
10
|
+
*
|
|
11
|
+
* 1. **An IME conversion is not a message.** Between `compositionstart` and `compositionend` the
|
|
12
|
+
* text in the box is a CANDIDATE being converted, and the `Enter` that accepts it belongs to
|
|
13
|
+
* the IME. A composer that reads that `Enter` as "send" makes Japanese and Vietnamese input
|
|
14
|
+
* impossible — the first kanji conversion sends a half-written line. The guard is a ref, not
|
|
15
|
+
* state, because it has to be readable inside the keydown that fires between the two events.
|
|
16
|
+
* 2. **One trailing action at a time.** While a response streams the send button IS the cancel
|
|
17
|
+
* button — the same discipline as the picker trailing-action rule. Rendering both is how a
|
|
18
|
+
* user cancels by aiming for send.
|
|
19
|
+
* 3. **The draft box is the focus target.** `ref`, `id`, `name` and the whole `FormField`
|
|
20
|
+
* label/helper/error contract land on the `<textarea>`, not on the frame, so a composer inside
|
|
21
|
+
* a `FormField` is labelled and described exactly like an `Input`.
|
|
22
|
+
*
|
|
23
|
+
* Everything visible is a real primitive: the draft box is `Textarea` (`variant="borderless"`, so
|
|
24
|
+
* the frame is the only boundary) and every action is a `Button`.
|
|
25
|
+
*/
|
|
26
|
+
export declare const ChatComposer: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<HTMLDivElement>, "defaultValue" | "onChange" | "onKeyDown" | "onSubmit" | "prefix"> & import("../../lib/field-a11y.js").FieldA11yProps & {
|
|
27
|
+
value?: import("../../props/index.js").ValueProp<string>;
|
|
28
|
+
defaultValue?: import("../../props/index.js").DefaultValueProp<string>;
|
|
29
|
+
onValueChange?: import("../../props/index.js").OnValueChangeProp<string>;
|
|
30
|
+
onSubmit?: (value: string) => void;
|
|
31
|
+
onCancel?: () => void;
|
|
32
|
+
loading?: import("../../props/index.js").PendingProp;
|
|
33
|
+
submitType?: import("./chat-composer.js").ChatComposerSubmitTypeProp;
|
|
34
|
+
placeholder?: import("../../props/index.js").PlaceholderProp;
|
|
35
|
+
disabled?: import("../../props/index.js").DisabledProp;
|
|
36
|
+
readOnly?: boolean;
|
|
37
|
+
header?: React.ReactNode;
|
|
38
|
+
prefix?: React.ReactNode;
|
|
39
|
+
footer?: React.ReactNode;
|
|
40
|
+
actions?: React.ReactNode;
|
|
41
|
+
size?: import("../../props/index.js").SizeProp;
|
|
42
|
+
maxLength?: number;
|
|
43
|
+
status?: import("../../props/index.js").ControlStatusProp;
|
|
44
|
+
submitLabel?: string;
|
|
45
|
+
cancelLabel?: string;
|
|
46
|
+
onKeyDown?: React.KeyboardEventHandler<HTMLTextAreaElement>;
|
|
47
|
+
name?: import("../../props/index.js").NameProp;
|
|
48
|
+
id?: import("../../props/index.js").IdProp;
|
|
49
|
+
className?: import("../../props/index.js").ClassNameProp;
|
|
50
|
+
} & React.RefAttributes<HTMLTextAreaElement>>;
|