@godxjp/ui 20.2.1 → 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/i18n/messages/en.json +45 -0
- package/dist/i18n/messages/ja.json +43 -0
- package/dist/i18n/messages/vi.json +44 -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 +2 -1
- 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 +1 -0
- 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,285 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { useTranslation } from "../../i18n/use-translation.js";
|
|
5
|
+
import { cn } from "../../lib/utils.js";
|
|
6
|
+
import { VisuallyHidden } from "../general/visually-hidden.js";
|
|
7
|
+
import { Popover, PopoverAnchor, PopoverContent } from "../data-display/popover.js";
|
|
8
|
+
import { Command, CommandEmpty, CommandItem, CommandList } from "./command.js";
|
|
9
|
+
function matchTrigger(text, caret, trigger) {
|
|
10
|
+
if (!trigger) return null;
|
|
11
|
+
const before = text.slice(0, caret);
|
|
12
|
+
const start = before.lastIndexOf(trigger);
|
|
13
|
+
if (start === -1) return null;
|
|
14
|
+
const query = before.slice(start + trigger.length);
|
|
15
|
+
if (/\s/.test(query)) return null;
|
|
16
|
+
const preceding = start === 0 ? "" : before[start - 1];
|
|
17
|
+
if (preceding !== "" && !/\s/.test(preceding)) return null;
|
|
18
|
+
return { query };
|
|
19
|
+
}
|
|
20
|
+
function levelItems(items, path) {
|
|
21
|
+
let current = items;
|
|
22
|
+
for (const step of path) {
|
|
23
|
+
const next = current.find((item) => item.value === step)?.children;
|
|
24
|
+
if (!next) return current;
|
|
25
|
+
current = next;
|
|
26
|
+
}
|
|
27
|
+
return current;
|
|
28
|
+
}
|
|
29
|
+
function labelOf(item) {
|
|
30
|
+
return item.label ?? item.value;
|
|
31
|
+
}
|
|
32
|
+
function matches(item, query) {
|
|
33
|
+
if (!query) return true;
|
|
34
|
+
const needle = query.toLocaleLowerCase();
|
|
35
|
+
return labelOf(item).toLocaleLowerCase().includes(needle) || item.value.toLocaleLowerCase().includes(needle) || (item.description?.toLocaleLowerCase().includes(needle) ?? false);
|
|
36
|
+
}
|
|
37
|
+
function ChatSuggestion({
|
|
38
|
+
items,
|
|
39
|
+
onValueChange,
|
|
40
|
+
triggerCharacter = "/",
|
|
41
|
+
open,
|
|
42
|
+
defaultOpen,
|
|
43
|
+
onOpenChange,
|
|
44
|
+
children,
|
|
45
|
+
emptyMessage,
|
|
46
|
+
listLabel: listLabelProp,
|
|
47
|
+
id,
|
|
48
|
+
className
|
|
49
|
+
}) {
|
|
50
|
+
const { t } = useTranslation();
|
|
51
|
+
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen ?? false);
|
|
52
|
+
const isOpen = open ?? uncontrolledOpen;
|
|
53
|
+
const [query, setQuery] = React.useState("");
|
|
54
|
+
const [path, setPath] = React.useState([]);
|
|
55
|
+
const [activeValue, setActiveValue] = React.useState("");
|
|
56
|
+
const [activeId, setActiveId] = React.useState(void 0);
|
|
57
|
+
const anchorRef = React.useRef(null);
|
|
58
|
+
const contentRef = React.useRef(null);
|
|
59
|
+
const [listId, setListId] = React.useState(void 0);
|
|
60
|
+
const field = React.useCallback(() => anchorRef.current?.querySelector("textarea") ?? null, []);
|
|
61
|
+
const openRef = React.useRef(isOpen);
|
|
62
|
+
openRef.current = isOpen;
|
|
63
|
+
const setOpen = React.useCallback(
|
|
64
|
+
(next) => {
|
|
65
|
+
if (openRef.current === next) return;
|
|
66
|
+
openRef.current = next;
|
|
67
|
+
if (open === void 0) setUncontrolledOpen(next);
|
|
68
|
+
onOpenChange?.(next);
|
|
69
|
+
if (!next) {
|
|
70
|
+
setQuery("");
|
|
71
|
+
setPath([]);
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
[open, onOpenChange]
|
|
75
|
+
);
|
|
76
|
+
const visible = React.useMemo(() => {
|
|
77
|
+
return levelItems(items, path).filter((item) => matches(item, query));
|
|
78
|
+
}, [items, path, query]);
|
|
79
|
+
const enabled = React.useMemo(() => visible.filter((item) => !item.disabled), [visible]);
|
|
80
|
+
React.useEffect(() => {
|
|
81
|
+
if (enabled.length === 0) {
|
|
82
|
+
setActiveValue("");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
setActiveValue(
|
|
86
|
+
(current) => enabled.some((item) => item.value === current) ? current : enabled[0].value
|
|
87
|
+
);
|
|
88
|
+
}, [enabled]);
|
|
89
|
+
React.useEffect(() => {
|
|
90
|
+
if (!isOpen) {
|
|
91
|
+
setListId(void 0);
|
|
92
|
+
setActiveId(void 0);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const content = contentRef.current;
|
|
96
|
+
setListId(content?.querySelector("[cmdk-list]")?.id || void 0);
|
|
97
|
+
setActiveId(content?.querySelector('[cmdk-item][aria-selected="true"]')?.id || void 0);
|
|
98
|
+
});
|
|
99
|
+
const close = React.useCallback(() => {
|
|
100
|
+
setOpen(false);
|
|
101
|
+
field()?.focus();
|
|
102
|
+
}, [field, setOpen]);
|
|
103
|
+
const evaluate = React.useCallback(() => {
|
|
104
|
+
const node = field();
|
|
105
|
+
if (!node) return;
|
|
106
|
+
const caret = node.selectionStart ?? node.value.length;
|
|
107
|
+
const hit = matchTrigger(node.value, caret, triggerCharacter);
|
|
108
|
+
if (!hit) {
|
|
109
|
+
setOpen(false);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
setQuery(hit.query);
|
|
113
|
+
setOpen(true);
|
|
114
|
+
}, [field, setOpen, triggerCharacter]);
|
|
115
|
+
const onTrigger = React.useCallback(
|
|
116
|
+
(next) => {
|
|
117
|
+
if (next === false) {
|
|
118
|
+
setOpen(false);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (next === void 0) {
|
|
122
|
+
setQuery("");
|
|
123
|
+
setOpen(true);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
window.setTimeout(evaluate, 0);
|
|
127
|
+
},
|
|
128
|
+
[evaluate, setOpen]
|
|
129
|
+
);
|
|
130
|
+
const pick = React.useCallback(
|
|
131
|
+
(item) => {
|
|
132
|
+
if (item.disabled) return;
|
|
133
|
+
if (item.children && item.children.length > 0) {
|
|
134
|
+
setPath((current) => [...current, item.value]);
|
|
135
|
+
setQuery("");
|
|
136
|
+
field()?.focus();
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
onValueChange?.(item.value);
|
|
140
|
+
setOpen(false);
|
|
141
|
+
field()?.focus();
|
|
142
|
+
},
|
|
143
|
+
[field, onValueChange, setOpen]
|
|
144
|
+
);
|
|
145
|
+
const move = React.useCallback(
|
|
146
|
+
(direction) => {
|
|
147
|
+
if (enabled.length === 0) return;
|
|
148
|
+
const index = enabled.findIndex((item) => item.value === activeValue);
|
|
149
|
+
const from = index === -1 ? direction === 1 ? -1 : 0 : index;
|
|
150
|
+
const next = (from + direction + enabled.length) % enabled.length;
|
|
151
|
+
setActiveValue(enabled[next].value);
|
|
152
|
+
},
|
|
153
|
+
[activeValue, enabled]
|
|
154
|
+
);
|
|
155
|
+
const onKeyDown = React.useCallback(
|
|
156
|
+
(event) => {
|
|
157
|
+
if (!openRef.current) {
|
|
158
|
+
if (event.key.startsWith("Arrow") || event.key === "Home" || event.key === "End") {
|
|
159
|
+
window.setTimeout(evaluate, 0);
|
|
160
|
+
}
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
switch (event.key) {
|
|
164
|
+
case "ArrowDown":
|
|
165
|
+
event.preventDefault();
|
|
166
|
+
move(1);
|
|
167
|
+
return;
|
|
168
|
+
case "ArrowUp":
|
|
169
|
+
event.preventDefault();
|
|
170
|
+
move(-1);
|
|
171
|
+
return;
|
|
172
|
+
case "Enter":
|
|
173
|
+
case "Tab": {
|
|
174
|
+
const item = enabled.find((candidate) => candidate.value === activeValue);
|
|
175
|
+
if (!item) return;
|
|
176
|
+
event.preventDefault();
|
|
177
|
+
pick(item);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
case "Escape":
|
|
181
|
+
event.preventDefault();
|
|
182
|
+
event.stopPropagation();
|
|
183
|
+
close();
|
|
184
|
+
return;
|
|
185
|
+
default:
|
|
186
|
+
if (event.key.startsWith("Arrow") || event.key === "Home" || event.key === "End") {
|
|
187
|
+
window.setTimeout(evaluate, 0);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
[activeValue, close, enabled, evaluate, move, pick]
|
|
192
|
+
);
|
|
193
|
+
const listLabel = listLabelProp ?? t("dataEntry.chatSuggestion.label");
|
|
194
|
+
return /* @__PURE__ */ jsxs(Popover, { open: isOpen, onOpenChange: setOpen, children: [
|
|
195
|
+
/* @__PURE__ */ jsxs(
|
|
196
|
+
PopoverAnchor,
|
|
197
|
+
{
|
|
198
|
+
ref: anchorRef,
|
|
199
|
+
id,
|
|
200
|
+
"data-slot": "chat-suggestion",
|
|
201
|
+
className: cn("block w-full", className),
|
|
202
|
+
onPointerUp: () => window.setTimeout(evaluate, 0),
|
|
203
|
+
onBlurCapture: (event) => {
|
|
204
|
+
const next = event.relatedTarget;
|
|
205
|
+
if (next && anchorRef.current?.contains(next)) return;
|
|
206
|
+
if (openRef.current) setOpen(false);
|
|
207
|
+
},
|
|
208
|
+
children: [
|
|
209
|
+
children({
|
|
210
|
+
onTrigger,
|
|
211
|
+
onKeyDown
|
|
212
|
+
}),
|
|
213
|
+
/* @__PURE__ */ jsx(FieldPopupWiring, { field, open: isOpen, listId, activeId }),
|
|
214
|
+
/* @__PURE__ */ jsx(VisuallyHidden, { role: "status", "aria-live": "polite", children: isOpen ? t("dataEntry.chatSuggestion.count", { count: visible.length }) : "" })
|
|
215
|
+
]
|
|
216
|
+
}
|
|
217
|
+
),
|
|
218
|
+
/* @__PURE__ */ jsx(
|
|
219
|
+
PopoverContent,
|
|
220
|
+
{
|
|
221
|
+
ref: contentRef,
|
|
222
|
+
flush: true,
|
|
223
|
+
align: "start",
|
|
224
|
+
"aria-label": listLabel,
|
|
225
|
+
className: "ui-chat-suggestion-panel",
|
|
226
|
+
onOpenAutoFocus: (event) => event.preventDefault(),
|
|
227
|
+
onCloseAutoFocus: (event) => event.preventDefault(),
|
|
228
|
+
children: /* @__PURE__ */ jsx(
|
|
229
|
+
Command,
|
|
230
|
+
{
|
|
231
|
+
value: activeValue,
|
|
232
|
+
onValueChange: setActiveValue,
|
|
233
|
+
shouldFilter: false,
|
|
234
|
+
label: listLabel,
|
|
235
|
+
children: /* @__PURE__ */ jsxs(CommandList, { label: listLabel, children: [
|
|
236
|
+
/* @__PURE__ */ jsx(CommandEmpty, { children: emptyMessage ?? t("dataEntry.chatSuggestion.empty") }),
|
|
237
|
+
visible.map((item) => /* @__PURE__ */ jsxs(
|
|
238
|
+
CommandItem,
|
|
239
|
+
{
|
|
240
|
+
value: item.value,
|
|
241
|
+
disabled: item.disabled,
|
|
242
|
+
onSelect: () => pick(item),
|
|
243
|
+
children: [
|
|
244
|
+
item.icon ? /* @__PURE__ */ jsx("span", { className: "flex shrink-0 items-center", "aria-hidden": "true", children: item.icon }) : null,
|
|
245
|
+
/* @__PURE__ */ jsxs("span", { className: "ui-chat-suggestion-item-text", children: [
|
|
246
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: labelOf(item) }),
|
|
247
|
+
item.description ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground truncate text-xs", children: item.description }) : null
|
|
248
|
+
] })
|
|
249
|
+
]
|
|
250
|
+
},
|
|
251
|
+
item.value
|
|
252
|
+
))
|
|
253
|
+
] })
|
|
254
|
+
}
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
)
|
|
258
|
+
] });
|
|
259
|
+
}
|
|
260
|
+
ChatSuggestion.displayName = "ChatSuggestion";
|
|
261
|
+
function FieldPopupWiring({
|
|
262
|
+
field,
|
|
263
|
+
open,
|
|
264
|
+
listId,
|
|
265
|
+
activeId
|
|
266
|
+
}) {
|
|
267
|
+
React.useEffect(() => {
|
|
268
|
+
const node = field();
|
|
269
|
+
if (!node) return;
|
|
270
|
+
node.setAttribute("aria-haspopup", "listbox");
|
|
271
|
+
node.setAttribute("aria-autocomplete", "list");
|
|
272
|
+
if (open && listId) {
|
|
273
|
+
node.setAttribute("aria-controls", listId);
|
|
274
|
+
if (activeId) node.setAttribute("aria-activedescendant", activeId);
|
|
275
|
+
else node.removeAttribute("aria-activedescendant");
|
|
276
|
+
} else {
|
|
277
|
+
node.removeAttribute("aria-controls");
|
|
278
|
+
node.removeAttribute("aria-activedescendant");
|
|
279
|
+
}
|
|
280
|
+
}, [activeId, field, listId, open]);
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
export {
|
|
284
|
+
ChatSuggestion
|
|
285
|
+
};
|
|
@@ -7,6 +7,10 @@ export { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScro
|
|
|
7
7
|
export { Checkbox } from "./checkbox.js";
|
|
8
8
|
export { CheckboxGroup } from "./checkbox-group.js";
|
|
9
9
|
export { Radio, RadioGroup, RadioItem, RadioGroupRoot } from "./radio.js";
|
|
10
|
+
export { ChatComposer } from "./chat-composer.js";
|
|
11
|
+
export type { ChatComposerProp, ChatComposerProps, ChatComposerSubmitTypeProp, } from "./chat-composer.js";
|
|
12
|
+
export { ChatSuggestion } from "./chat-suggestion.js";
|
|
13
|
+
export type { ChatSuggestionProp, ChatSuggestionProps, ChatSuggestionItemProp, ChatSuggestionRenderProp, } from "./chat-suggestion.js";
|
|
10
14
|
export { Textarea } from "./textarea.js";
|
|
11
15
|
export type { TextareaProps } from "./textarea.js";
|
|
12
16
|
export { Form, useFormLayout, type FormLayoutContextValue } from "./form.js";
|
|
@@ -16,6 +16,8 @@ import {
|
|
|
16
16
|
import { Checkbox } from "./checkbox.js";
|
|
17
17
|
import { CheckboxGroup } from "./checkbox-group.js";
|
|
18
18
|
import { Radio, RadioGroup, RadioItem, RadioGroupRoot } from "./radio.js";
|
|
19
|
+
import { ChatComposer } from "./chat-composer.js";
|
|
20
|
+
import { ChatSuggestion } from "./chat-suggestion.js";
|
|
19
21
|
import { Textarea } from "./textarea.js";
|
|
20
22
|
import { Form, useFormLayout } from "./form.js";
|
|
21
23
|
import { FormField } from "./form-field.js";
|
|
@@ -64,6 +66,8 @@ export {
|
|
|
64
66
|
BranchScopePicker,
|
|
65
67
|
Calendar,
|
|
66
68
|
Cascader,
|
|
69
|
+
ChatComposer,
|
|
70
|
+
ChatSuggestion,
|
|
67
71
|
Checkbox,
|
|
68
72
|
CheckboxGroup,
|
|
69
73
|
ColorPicker,
|
|
@@ -158,11 +158,13 @@ const Textarea = React.forwardRef(
|
|
|
158
158
|
}
|
|
159
159
|
);
|
|
160
160
|
if (!needsWrapper) return field;
|
|
161
|
+
const placeholderText = typeof props.placeholder === "string" ? props.placeholder : void 0;
|
|
162
|
+
const mirrorText = mirror.length > 0 ? mirror : placeholderText ?? "";
|
|
161
163
|
return /* @__PURE__ */ jsxs(
|
|
162
164
|
"span",
|
|
163
165
|
{
|
|
164
166
|
"data-slot": "textarea-affix-wrapper",
|
|
165
|
-
"data-autogrow-value": growing ?
|
|
167
|
+
"data-autogrow-value": growing ? mirrorText : void 0,
|
|
166
168
|
style: { ...autoGrowVars, ...mirrorInset },
|
|
167
169
|
className: cn(
|
|
168
170
|
"relative w-full",
|
|
@@ -1,48 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
};
|
|
12
|
-
export type TreeFieldNames = {
|
|
13
|
-
label?: string;
|
|
14
|
-
value?: string;
|
|
15
|
-
children?: string;
|
|
16
|
-
};
|
|
17
|
-
export type NormalizedTreeOption = TreeOption & {
|
|
18
|
-
children?: NormalizedTreeOption[];
|
|
19
|
-
};
|
|
20
|
-
type RawTreeNode = Record<string, unknown>;
|
|
21
|
-
export declare function reactNodeText(value: React.ReactNode): string;
|
|
22
|
-
export declare function normalizeTreeOptions(nodes: RawTreeNode[] | undefined, fieldNames?: TreeFieldNames): NormalizedTreeOption[];
|
|
23
|
-
export declare function getNodeByPath(options: NormalizedTreeOption[], path: string[]): NormalizedTreeOption[];
|
|
24
|
-
export declare function getOptionsAtPath(options: NormalizedTreeOption[], path: string[]): NormalizedTreeOption[];
|
|
25
|
-
export declare function formatPathLabels(chain: NormalizedTreeOption[], separator?: string): string;
|
|
26
|
-
export type TreePath = {
|
|
27
|
-
path: string[];
|
|
28
|
-
labels: string[];
|
|
29
|
-
};
|
|
30
|
-
export declare function collectLeafPaths(options: NormalizedTreeOption[], prefix?: string[], root?: NormalizedTreeOption[]): TreePath[];
|
|
31
|
-
export declare function collectAllPaths(options: NormalizedTreeOption[], prefix?: string[], root?: NormalizedTreeOption[]): TreePath[];
|
|
32
|
-
export declare function pathKey(path: string[]): string;
|
|
33
|
-
export declare function pathsEqual(a: string[], b: string[]): boolean;
|
|
34
|
-
export declare function filterTreeOptions(options: NormalizedTreeOption[], query: string, filter?: (query: string, path: NormalizedTreeOption[]) => boolean): TreePath[];
|
|
35
|
-
export declare function getDescendantValues(node: NormalizedTreeOption): string[];
|
|
36
|
-
export declare function flattenVisibleTree(options: NormalizedTreeOption[], expandedKeys: Set<string>, depth?: number): {
|
|
37
|
-
node: NormalizedTreeOption;
|
|
38
|
-
depth: number;
|
|
39
|
-
hasChildren: boolean;
|
|
40
|
-
}[];
|
|
41
|
-
export declare function filterVisibleTree(options: NormalizedTreeOption[], query: string): {
|
|
42
|
-
node: NormalizedTreeOption;
|
|
43
|
-
depth: number;
|
|
44
|
-
hasChildren: boolean;
|
|
45
|
-
}[];
|
|
46
|
-
export declare function collectAllExpandableKeys(options: NormalizedTreeOption[]): string[];
|
|
47
|
-
export declare function findNodeByValue(options: NormalizedTreeOption[], value: string): NormalizedTreeOption | undefined;
|
|
48
|
-
export {};
|
|
1
|
+
/**
|
|
2
|
+
* RE-EXPORT SHIM — the tree model moved to `src/lib/tree.ts`.
|
|
3
|
+
*
|
|
4
|
+
* `Tree` (data-display) and `TreeSelect` (data-entry) are two SURFACES over ONE model. Forking the
|
|
5
|
+
* traversal would let a page tree and a dropdown tree disagree about what "expanded", "a leaf" or
|
|
6
|
+
* "every descendant" means — so the normalizer, the visible-row flattener and the descendant walk
|
|
7
|
+
* live in `src/lib/`, which both groups may import, and this path stays valid so nothing that
|
|
8
|
+
* already imports `./tree-utils` has to change.
|
|
9
|
+
*/
|
|
10
|
+
export * from "../../lib/tree.js";
|
|
@@ -1,154 +1 @@
|
|
|
1
|
-
|
|
2
|
-
if (value == null || typeof value === "boolean") return "";
|
|
3
|
-
if (typeof value === "string" || typeof value === "number" || typeof value === "bigint") {
|
|
4
|
-
return String(value);
|
|
5
|
-
}
|
|
6
|
-
if (Array.isArray(value)) {
|
|
7
|
-
return value.map((item) => reactNodeText(item)).join("");
|
|
8
|
-
}
|
|
9
|
-
return "";
|
|
10
|
-
}
|
|
11
|
-
function unknownText(value) {
|
|
12
|
-
if (typeof value === "string" || typeof value === "number" || typeof value === "bigint") {
|
|
13
|
-
return String(value);
|
|
14
|
-
}
|
|
15
|
-
return "";
|
|
16
|
-
}
|
|
17
|
-
function normalizeTreeOptions(nodes, fieldNames) {
|
|
18
|
-
if (!nodes?.length) return [];
|
|
19
|
-
const labelKey = fieldNames?.label ?? "label";
|
|
20
|
-
const valueKey = fieldNames?.value ?? "value";
|
|
21
|
-
const childrenKey = fieldNames?.children ?? "children";
|
|
22
|
-
return nodes.map((node) => {
|
|
23
|
-
const children = node[childrenKey];
|
|
24
|
-
const value = unknownText(node[valueKey]);
|
|
25
|
-
const label = node[labelKey];
|
|
26
|
-
return {
|
|
27
|
-
value,
|
|
28
|
-
label: label ?? value,
|
|
29
|
-
disabled: Boolean(node.disabled),
|
|
30
|
-
disableCheckbox: Boolean(node.disableCheckbox),
|
|
31
|
-
isLeaf: node.isLeaf,
|
|
32
|
-
children: Array.isArray(children) ? normalizeTreeOptions(children, fieldNames) : void 0
|
|
33
|
-
};
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
function getNodeByPath(options, path) {
|
|
37
|
-
const chain = [];
|
|
38
|
-
let level = options;
|
|
39
|
-
for (const segment of path) {
|
|
40
|
-
const found = level.find((n) => n.value === segment);
|
|
41
|
-
if (!found) break;
|
|
42
|
-
chain.push(found);
|
|
43
|
-
level = found.children ?? [];
|
|
44
|
-
}
|
|
45
|
-
return chain;
|
|
46
|
-
}
|
|
47
|
-
function getOptionsAtPath(options, path) {
|
|
48
|
-
if (!path.length) return options;
|
|
49
|
-
const chain = getNodeByPath(options, path);
|
|
50
|
-
return chain.at(-1)?.children ?? [];
|
|
51
|
-
}
|
|
52
|
-
function formatPathLabels(chain, separator = " / ") {
|
|
53
|
-
return chain.map((n) => reactNodeText(n.label)).join(separator);
|
|
54
|
-
}
|
|
55
|
-
function collectLeafPaths(options, prefix = [], root = options) {
|
|
56
|
-
const out = [];
|
|
57
|
-
for (const node of options) {
|
|
58
|
-
const path = [...prefix, node.value];
|
|
59
|
-
const hasChildren = (node.children?.length ?? 0) > 0;
|
|
60
|
-
if (!hasChildren || node.isLeaf === true) {
|
|
61
|
-
out.push({ path, labels: getNodeByPath(root, path).map((n) => reactNodeText(n.label)) });
|
|
62
|
-
}
|
|
63
|
-
if (hasChildren) out.push(...collectLeafPaths(node.children, path, root));
|
|
64
|
-
}
|
|
65
|
-
return out;
|
|
66
|
-
}
|
|
67
|
-
function collectAllPaths(options, prefix = [], root = options) {
|
|
68
|
-
const out = [];
|
|
69
|
-
for (const node of options) {
|
|
70
|
-
const path = [...prefix, node.value];
|
|
71
|
-
out.push({ path, labels: getNodeByPath(root, path).map((n) => reactNodeText(n.label)) });
|
|
72
|
-
if (node.children?.length) out.push(...collectAllPaths(node.children, path, root));
|
|
73
|
-
}
|
|
74
|
-
return out;
|
|
75
|
-
}
|
|
76
|
-
function pathKey(path) {
|
|
77
|
-
return path.join("\0");
|
|
78
|
-
}
|
|
79
|
-
function pathsEqual(a, b) {
|
|
80
|
-
return a.length === b.length && a.every((v, i) => v === b[i]);
|
|
81
|
-
}
|
|
82
|
-
function filterTreeOptions(options, query, filter) {
|
|
83
|
-
const q = query.trim().toLowerCase();
|
|
84
|
-
if (!q) return [];
|
|
85
|
-
const paths = collectLeafPaths(options);
|
|
86
|
-
return paths.filter(({ path }) => {
|
|
87
|
-
const chain = getNodeByPath(options, path);
|
|
88
|
-
if (filter) return filter(query, chain);
|
|
89
|
-
return chain.some((n) => reactNodeText(n.label).toLowerCase().includes(q));
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
function getDescendantValues(node) {
|
|
93
|
-
const values = [node.value];
|
|
94
|
-
for (const child of node.children ?? []) values.push(...getDescendantValues(child));
|
|
95
|
-
return values;
|
|
96
|
-
}
|
|
97
|
-
function flattenVisibleTree(options, expandedKeys, depth = 0) {
|
|
98
|
-
const out = [];
|
|
99
|
-
for (const node of options) {
|
|
100
|
-
const hasChildren = (node.children?.length ?? 0) > 0 && node.isLeaf !== true;
|
|
101
|
-
out.push({ node, depth, hasChildren });
|
|
102
|
-
if (hasChildren && expandedKeys.has(node.value)) {
|
|
103
|
-
out.push(...flattenVisibleTree(node.children, expandedKeys, depth + 1));
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
return out;
|
|
107
|
-
}
|
|
108
|
-
function filterVisibleTree(options, query) {
|
|
109
|
-
const q = query.trim().toLowerCase();
|
|
110
|
-
if (!q) return flattenVisibleTree(options, new Set(collectAllExpandableKeys(options)));
|
|
111
|
-
function matches(nodes, depth) {
|
|
112
|
-
return nodes.flatMap((node) => {
|
|
113
|
-
const children = node.isLeaf ? [] : matches(node.children ?? [], depth + 1);
|
|
114
|
-
if (!reactNodeText(node.label).toLowerCase().includes(q) && children.length === 0) return [];
|
|
115
|
-
return [{ node, depth, hasChildren: children.length > 0 }, ...children];
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
return matches(options, 0);
|
|
119
|
-
}
|
|
120
|
-
function collectAllExpandableKeys(options) {
|
|
121
|
-
const keys = [];
|
|
122
|
-
for (const node of options) {
|
|
123
|
-
if ((node.children?.length ?? 0) > 0 && node.isLeaf !== true) {
|
|
124
|
-
keys.push(node.value);
|
|
125
|
-
keys.push(...collectAllExpandableKeys(node.children));
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
return keys;
|
|
129
|
-
}
|
|
130
|
-
function findNodeByValue(options, value) {
|
|
131
|
-
for (const node of options) {
|
|
132
|
-
if (node.value === value) return node;
|
|
133
|
-
const nested = node.children ? findNodeByValue(node.children, value) : void 0;
|
|
134
|
-
if (nested) return nested;
|
|
135
|
-
}
|
|
136
|
-
return void 0;
|
|
137
|
-
}
|
|
138
|
-
export {
|
|
139
|
-
collectAllExpandableKeys,
|
|
140
|
-
collectAllPaths,
|
|
141
|
-
collectLeafPaths,
|
|
142
|
-
filterTreeOptions,
|
|
143
|
-
filterVisibleTree,
|
|
144
|
-
findNodeByValue,
|
|
145
|
-
flattenVisibleTree,
|
|
146
|
-
formatPathLabels,
|
|
147
|
-
getDescendantValues,
|
|
148
|
-
getNodeByPath,
|
|
149
|
-
getOptionsAtPath,
|
|
150
|
-
normalizeTreeOptions,
|
|
151
|
-
pathKey,
|
|
152
|
-
pathsEqual,
|
|
153
|
-
reactNodeText
|
|
154
|
-
};
|
|
1
|
+
export * from "../../lib/tree.js";
|
|
@@ -162,6 +162,22 @@
|
|
|
162
162
|
"error": "Invalid",
|
|
163
163
|
"validating": "Validating",
|
|
164
164
|
"submitFailed": "Could not submit the form. Please try again."
|
|
165
|
+
},
|
|
166
|
+
"chatComposer": {
|
|
167
|
+
"label": "Message",
|
|
168
|
+
"placeholder": "Write a message",
|
|
169
|
+
"send": "Send message",
|
|
170
|
+
"cancel": "Stop generating",
|
|
171
|
+
"hintEnter": "Enter to send · Shift + Enter for a new line",
|
|
172
|
+
"hintShiftEnter": "Shift + Enter to send · Enter for a new line"
|
|
173
|
+
},
|
|
174
|
+
"chatSuggestion": {
|
|
175
|
+
"label": "Suggestions",
|
|
176
|
+
"empty": "No suggestions match",
|
|
177
|
+
"count": {
|
|
178
|
+
"one": "{count} suggestion",
|
|
179
|
+
"other": "{count} suggestions"
|
|
180
|
+
}
|
|
165
181
|
}
|
|
166
182
|
},
|
|
167
183
|
"feedback": {
|
|
@@ -289,6 +305,13 @@
|
|
|
289
305
|
"progress": {
|
|
290
306
|
"ariaLabel": "Progress",
|
|
291
307
|
"breakdownSeparator": ", "
|
|
308
|
+
},
|
|
309
|
+
"tree": {
|
|
310
|
+
"expand": "Expand",
|
|
311
|
+
"collapse": "Collapse",
|
|
312
|
+
"loading": "Loading child nodes…",
|
|
313
|
+
"empty": "Nothing to show",
|
|
314
|
+
"selected": "Selected"
|
|
292
315
|
}
|
|
293
316
|
},
|
|
294
317
|
"ui": {
|
|
@@ -536,5 +559,27 @@
|
|
|
536
559
|
"textExamples": {
|
|
537
560
|
"preservedText": "Plain text",
|
|
538
561
|
"sampleText": "First line\n Indented line\n\nLast line"
|
|
562
|
+
},
|
|
563
|
+
"chat": {
|
|
564
|
+
"bubble": {
|
|
565
|
+
"typing": "Typing…",
|
|
566
|
+
"loading": "Loading the message",
|
|
567
|
+
"you": "You",
|
|
568
|
+
"assistant": "Assistant",
|
|
569
|
+
"tone": {
|
|
570
|
+
"info": "Information",
|
|
571
|
+
"success": "Success",
|
|
572
|
+
"warning": "Warning",
|
|
573
|
+
"destructive": "Error"
|
|
574
|
+
}
|
|
575
|
+
},
|
|
576
|
+
"list": {
|
|
577
|
+
"label": "Conversation",
|
|
578
|
+
"jumpToLatest": "Jump to latest",
|
|
579
|
+
"newMessages": {
|
|
580
|
+
"one": "{count} new message",
|
|
581
|
+
"other": "{count} new messages"
|
|
582
|
+
}
|
|
583
|
+
}
|
|
539
584
|
}
|
|
540
585
|
}
|
|
@@ -159,6 +159,21 @@
|
|
|
159
159
|
"error": "入力エラー",
|
|
160
160
|
"validating": "確認中",
|
|
161
161
|
"submitFailed": "フォームを送信できませんでした。再試行してください。"
|
|
162
|
+
},
|
|
163
|
+
"chatComposer": {
|
|
164
|
+
"label": "メッセージ",
|
|
165
|
+
"placeholder": "メッセージを入力",
|
|
166
|
+
"send": "メッセージを送信",
|
|
167
|
+
"cancel": "生成を停止",
|
|
168
|
+
"hintEnter": "Enter で送信 · Shift + Enter で改行",
|
|
169
|
+
"hintShiftEnter": "Shift + Enter で送信 · Enter で改行"
|
|
170
|
+
},
|
|
171
|
+
"chatSuggestion": {
|
|
172
|
+
"label": "候補",
|
|
173
|
+
"empty": "一致する候補がありません",
|
|
174
|
+
"count": {
|
|
175
|
+
"other": "候補 {count} 件"
|
|
176
|
+
}
|
|
162
177
|
}
|
|
163
178
|
},
|
|
164
179
|
"feedback": {
|
|
@@ -283,6 +298,13 @@
|
|
|
283
298
|
"progress": {
|
|
284
299
|
"ariaLabel": "進捗",
|
|
285
300
|
"breakdownSeparator": "、"
|
|
301
|
+
},
|
|
302
|
+
"tree": {
|
|
303
|
+
"expand": "展開",
|
|
304
|
+
"collapse": "折りたたむ",
|
|
305
|
+
"loading": "子ノードを読み込み中…",
|
|
306
|
+
"empty": "表示する項目がありません",
|
|
307
|
+
"selected": "選択中"
|
|
286
308
|
}
|
|
287
309
|
},
|
|
288
310
|
"ui": {
|
|
@@ -524,5 +546,26 @@
|
|
|
524
546
|
"textExamples": {
|
|
525
547
|
"preservedText": "プレーンテキスト",
|
|
526
548
|
"sampleText": "最初の行\n 字下げした行\n\n最後の行"
|
|
549
|
+
},
|
|
550
|
+
"chat": {
|
|
551
|
+
"bubble": {
|
|
552
|
+
"typing": "入力中",
|
|
553
|
+
"loading": "メッセージを読み込み中",
|
|
554
|
+
"you": "自分",
|
|
555
|
+
"assistant": "アシスタント",
|
|
556
|
+
"tone": {
|
|
557
|
+
"info": "情報",
|
|
558
|
+
"success": "成功",
|
|
559
|
+
"warning": "警告",
|
|
560
|
+
"destructive": "エラー"
|
|
561
|
+
}
|
|
562
|
+
},
|
|
563
|
+
"list": {
|
|
564
|
+
"label": "会話",
|
|
565
|
+
"jumpToLatest": "最新へ移動",
|
|
566
|
+
"newMessages": {
|
|
567
|
+
"other": "新着 {count} 件"
|
|
568
|
+
}
|
|
569
|
+
}
|
|
527
570
|
}
|
|
528
571
|
}
|