@godxjp/ui 20.2.0 → 21.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/components/data-display/chat-bubble.d.ts +30 -0
- package/dist/components/data-display/chat-bubble.js +229 -0
- package/dist/components/data-display/descriptions.js +4 -1
- package/dist/components/data-display/index.d.ts +4 -2
- package/dist/components/data-display/index.js +5 -2
- package/dist/components/data-display/popover.js +3 -2
- 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/index.d.ts +4 -0
- package/dist/components/data-entry/index.js +4 -0
- 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/org-switcher.js +22 -2
- package/dist/i18n/messages/en.json +50 -0
- package/dist/i18n/messages/ja.json +48 -0
- package/dist/i18n/messages/vi.json +49 -0
- package/dist/lib/tree.d.ts +53 -0
- package/dist/lib/tree.js +155 -0
- package/dist/props/components/data-display.prop.d.ts +187 -1
- package/dist/props/components/data-entry.prop.d.ts +127 -0
- package/dist/props/registry.d.ts +140 -3
- package/dist/props/registry.js +202 -3
- package/dist/styles/control.css +7 -0
- package/dist/styles/data-display-layout.css +309 -55
- package/dist/styles/data-entry-layout.css +64 -0
- package/dist/styles/shell-layout.css +41 -11
- 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 +0 -6
- package/dist/tokens/components/descriptions.css +4 -0
- package/dist/tokens/components/shell.css +13 -3
- package/dist/tokens/components/tree.css +27 -0
- package/docs/FRAME-COVERAGE-LEDGER.md +1 -1
- package/docs/FRAME-COVERAGE-REPORT.md +8 -3
- package/docs/data-display/chat-bubble.tsx +397 -0
- package/docs/data-display/timeline.tsx +46 -0
- package/docs/data-display/tree.tsx +394 -0
- package/docs/data-entry/chat-composer.tsx +464 -0
- package/docs/data-entry/chat-suggestion.tsx +301 -0
- package/docs/roadmap/ai-chat-components.md +207 -0
- package/docs/roadmap/antd-parity.md +154 -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 +344 -0
- package/docs/roadmap/parity-audit-layout-navigation-general.md +464 -0
- package/docs/roadmap/parity-backlog.md +79 -0
- package/docs/roadmap/tree-components.md +151 -0
- package/docs/showcase/table-tree-rows.tsx +4 -4
- package/package.json +5 -3
- package/dist/components/data-display/tree-list.d.ts +0 -13
- package/dist/components/data-display/tree-list.js +0 -26
- package/docs/data-display/tree-list.tsx +0 -107
|
@@ -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>>;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { SendHorizontal, Square } from "lucide-react";
|
|
5
|
+
import { useTranslation } from "../../i18n/use-translation.js";
|
|
6
|
+
import { cn } from "../../lib/utils.js";
|
|
7
|
+
import { omitFieldA11y, pickFieldA11y, useFieldIdentity } from "../../lib/field-a11y.js";
|
|
8
|
+
import { Button } from "../general/button.js";
|
|
9
|
+
import { Textarea } from "./textarea.js";
|
|
10
|
+
import { controlSurfaceAttrs, resolveAriaInvalid } from "./control-surface.js";
|
|
11
|
+
function isSendable(text) {
|
|
12
|
+
return text.trim().length > 0;
|
|
13
|
+
}
|
|
14
|
+
const ChatComposer = React.forwardRef(
|
|
15
|
+
({
|
|
16
|
+
className,
|
|
17
|
+
id,
|
|
18
|
+
name,
|
|
19
|
+
value,
|
|
20
|
+
defaultValue,
|
|
21
|
+
onValueChange,
|
|
22
|
+
onSubmit,
|
|
23
|
+
onCancel,
|
|
24
|
+
loading = false,
|
|
25
|
+
submitType = "enter",
|
|
26
|
+
placeholder,
|
|
27
|
+
disabled = false,
|
|
28
|
+
readOnly = false,
|
|
29
|
+
header,
|
|
30
|
+
prefix,
|
|
31
|
+
footer,
|
|
32
|
+
actions,
|
|
33
|
+
size,
|
|
34
|
+
maxLength,
|
|
35
|
+
status,
|
|
36
|
+
submitLabel,
|
|
37
|
+
cancelLabel,
|
|
38
|
+
onKeyDown,
|
|
39
|
+
...props
|
|
40
|
+
}, ref) => {
|
|
41
|
+
const { t } = useTranslation();
|
|
42
|
+
const innerRef = React.useRef(null);
|
|
43
|
+
const setRefs = React.useCallback(
|
|
44
|
+
(node) => {
|
|
45
|
+
innerRef.current = node;
|
|
46
|
+
if (typeof ref === "function") ref(node);
|
|
47
|
+
else if (ref) ref.current = node;
|
|
48
|
+
},
|
|
49
|
+
[ref]
|
|
50
|
+
);
|
|
51
|
+
const [draft, setDraft] = React.useState(() => String(value ?? defaultValue ?? ""));
|
|
52
|
+
React.useEffect(() => {
|
|
53
|
+
if (value !== void 0) setDraft(String(value));
|
|
54
|
+
}, [value]);
|
|
55
|
+
const composing = React.useRef(false);
|
|
56
|
+
const fieldA11y = pickFieldA11y(props);
|
|
57
|
+
const rest = omitFieldA11y(props);
|
|
58
|
+
const identity = useFieldIdentity({ id, name, "data-field": props["data-field"] });
|
|
59
|
+
const surface = controlSurfaceAttrs({ status, size });
|
|
60
|
+
const canSubmit = isSendable(draft) && !disabled && !readOnly && !loading;
|
|
61
|
+
const submit = React.useCallback(() => {
|
|
62
|
+
const text = innerRef.current?.value ?? draft;
|
|
63
|
+
if (!isSendable(text) || disabled || readOnly || loading) return;
|
|
64
|
+
onSubmit?.(text);
|
|
65
|
+
}, [draft, disabled, readOnly, loading, onSubmit]);
|
|
66
|
+
const handleKeyDown = (event) => {
|
|
67
|
+
onKeyDown?.(event);
|
|
68
|
+
if (event.defaultPrevented) return;
|
|
69
|
+
if (event.key !== "Enter") return;
|
|
70
|
+
if (composing.current || event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const wantsSend = submitType === "enter" ? !event.shiftKey : event.shiftKey;
|
|
74
|
+
if (!wantsSend) return;
|
|
75
|
+
event.preventDefault();
|
|
76
|
+
submit();
|
|
77
|
+
};
|
|
78
|
+
const sendName = submitLabel ?? t("dataEntry.chatComposer.send");
|
|
79
|
+
const cancelName = cancelLabel ?? t("dataEntry.chatComposer.cancel");
|
|
80
|
+
const actionSize = size === "xs" ? "icon-xs" : size === "sm" ? "icon-sm" : size === "lg" ? "icon-lg" : "icon";
|
|
81
|
+
return /* @__PURE__ */ jsxs(
|
|
82
|
+
"div",
|
|
83
|
+
{
|
|
84
|
+
"data-slot": "chat-composer",
|
|
85
|
+
"data-disabled": disabled ? "" : void 0,
|
|
86
|
+
"data-loading": loading ? "" : void 0,
|
|
87
|
+
"data-submit-type": submitType,
|
|
88
|
+
"aria-busy": loading || void 0,
|
|
89
|
+
className: cn("ui-chat-composer ui-control-surface", className),
|
|
90
|
+
...rest,
|
|
91
|
+
...surface,
|
|
92
|
+
children: [
|
|
93
|
+
header ? /* @__PURE__ */ jsx("div", { "data-slot": "chat-composer-header", className: "ui-chat-composer-header", children: header }) : null,
|
|
94
|
+
/* @__PURE__ */ jsxs("div", { "data-slot": "chat-composer-row", className: "ui-chat-composer-row", children: [
|
|
95
|
+
prefix ? /* @__PURE__ */ jsx("div", { "data-slot": "chat-composer-prefix", className: "ui-chat-composer-prefix", children: prefix }) : null,
|
|
96
|
+
/* @__PURE__ */ jsx(
|
|
97
|
+
Textarea,
|
|
98
|
+
{
|
|
99
|
+
ref: setRefs,
|
|
100
|
+
id,
|
|
101
|
+
name,
|
|
102
|
+
variant: "borderless",
|
|
103
|
+
autoGrow: true,
|
|
104
|
+
className: "ui-chat-composer-field",
|
|
105
|
+
value,
|
|
106
|
+
defaultValue,
|
|
107
|
+
placeholder: placeholder ?? t("dataEntry.chatComposer.placeholder"),
|
|
108
|
+
disabled,
|
|
109
|
+
readOnly,
|
|
110
|
+
maxLength,
|
|
111
|
+
status,
|
|
112
|
+
"aria-invalid": resolveAriaInvalid(fieldA11y["aria-invalid"], status),
|
|
113
|
+
onValueChange: (next) => {
|
|
114
|
+
if (value === void 0) setDraft(next);
|
|
115
|
+
onValueChange?.(next);
|
|
116
|
+
},
|
|
117
|
+
onKeyDown: handleKeyDown,
|
|
118
|
+
onCompositionStart: () => {
|
|
119
|
+
composing.current = true;
|
|
120
|
+
},
|
|
121
|
+
onCompositionEnd: () => {
|
|
122
|
+
composing.current = false;
|
|
123
|
+
},
|
|
124
|
+
...fieldA11y,
|
|
125
|
+
...identity
|
|
126
|
+
}
|
|
127
|
+
),
|
|
128
|
+
/* @__PURE__ */ jsxs("div", { "data-slot": "chat-composer-actions", className: "ui-chat-composer-actions", children: [
|
|
129
|
+
actions,
|
|
130
|
+
loading ? /* @__PURE__ */ jsx(
|
|
131
|
+
Button,
|
|
132
|
+
{
|
|
133
|
+
type: "button",
|
|
134
|
+
size: actionSize,
|
|
135
|
+
variant: "secondary",
|
|
136
|
+
"aria-label": cancelName,
|
|
137
|
+
onClick: onCancel,
|
|
138
|
+
disabled,
|
|
139
|
+
children: /* @__PURE__ */ jsx(Square, { "aria-hidden": "true" })
|
|
140
|
+
}
|
|
141
|
+
) : /* @__PURE__ */ jsx(
|
|
142
|
+
Button,
|
|
143
|
+
{
|
|
144
|
+
type: "button",
|
|
145
|
+
size: actionSize,
|
|
146
|
+
"aria-label": sendName,
|
|
147
|
+
onClick: submit,
|
|
148
|
+
disabled: !canSubmit,
|
|
149
|
+
children: /* @__PURE__ */ jsx(SendHorizontal, { "aria-hidden": "true" })
|
|
150
|
+
}
|
|
151
|
+
)
|
|
152
|
+
] })
|
|
153
|
+
] }),
|
|
154
|
+
footer ? /* @__PURE__ */ jsx("div", { "data-slot": "chat-composer-footer", className: "ui-chat-composer-footer", children: footer }) : null
|
|
155
|
+
]
|
|
156
|
+
}
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
);
|
|
160
|
+
ChatComposer.displayName = "ChatComposer";
|
|
161
|
+
export {
|
|
162
|
+
ChatComposer
|
|
163
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { ChatSuggestionProp } from "../../props/components/data-entry.prop.js";
|
|
3
|
+
export type { ChatSuggestionProp, ChatSuggestionProp as ChatSuggestionProps, ChatSuggestionItemProp, ChatSuggestionRenderProp, } from "../../props/components/data-entry.prop.js";
|
|
4
|
+
/**
|
|
5
|
+
* ChatSuggestion — trigger-character autocomplete over a `ChatComposer` (Ant Design X
|
|
6
|
+
* `Suggestion`).
|
|
7
|
+
*
|
|
8
|
+
* ## What it owns, and what it deliberately does not
|
|
9
|
+
*
|
|
10
|
+
* The list is the existing `Command` (cmdk) inside a `Popover`: the listbox/option roles, the
|
|
11
|
+
* active-row bookkeeping and the scroll-into-view all come from a primitive that already ships
|
|
12
|
+
* them, so there is no hand-rolled listbox here. What this component owns is the part `Command`
|
|
13
|
+
* cannot know about — a `<textarea>` it does not render:
|
|
14
|
+
*
|
|
15
|
+
* - **Detecting the trigger at the CARET**, not merely anywhere in the text, so a `/` in the
|
|
16
|
+
* middle of a URL opens nothing and a caret moved out of a token closes the list.
|
|
17
|
+
* - **Driving the list from a box that keeps focus.** Focus never leaves the textarea (the popover
|
|
18
|
+
* is opened with `onOpenAutoFocus` prevented), so arrows/Enter are forwarded through the render
|
|
19
|
+
* prop and the active row is announced through `aria-activedescendant` — the APG combobox
|
|
20
|
+
* pattern, rather than moving focus into the panel and stranding the draft.
|
|
21
|
+
* - **`Escape` costs nothing.** It closes the list, returns focus to the textarea and leaves the
|
|
22
|
+
* typed text exactly as it was; a suggestion list that eats the draft on dismiss is the defect
|
|
23
|
+
* this guards.
|
|
24
|
+
*/
|
|
25
|
+
export declare function ChatSuggestion({ items, onValueChange, triggerCharacter, open, defaultOpen, onOpenChange, children, emptyMessage, listLabel: listLabelProp, id, className, }: ChatSuggestionProp): React.JSX.Element;
|
|
26
|
+
export declare namespace ChatSuggestion {
|
|
27
|
+
var displayName: string;
|
|
28
|
+
}
|