@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
|
@@ -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
|
+
}
|
|
@@ -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
|
+
};
|
|
@@ -2,8 +2,15 @@ import * as React from "react";
|
|
|
2
2
|
import type { DatePickerProp } from "../../props/components/data-entry.prop.js";
|
|
3
3
|
export type { DatePickerProp, DatePickerProp as DatePickerProps, } from "../../props/components/data-entry.prop.js";
|
|
4
4
|
/**
|
|
5
|
-
* DatePicker — WAI-ARIA date combobox. A real, typeable `<input>` holds the
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* DatePicker — ONE date control, WAI-ARIA date combobox. A real, typeable `<input>` holds the
|
|
6
|
+
* value (form-submittable via `name`, screen-reader friendly, e2e-testable by filling the input);
|
|
7
|
+
* the panel is the visual affordance.
|
|
8
|
+
*
|
|
9
|
+
* Two axes: `picker` sets the GRANULARITY (day · week · month · quarter · year), `range` sets the
|
|
10
|
+
* CARDINALITY (one date, `multiple` dates, or a two-endpoint `DateRange`). This replaces the four
|
|
11
|
+
* components this package used to ship — `DateRangePicker`, `MonthPicker`, `MonthRangePicker` were
|
|
12
|
+
* separate copies of this same machine, and every one of them had drifted: a disabled picker that
|
|
13
|
+
* still opened, bounds that greyed a chevron and clamped nothing, an unhandled Enter key, and a
|
|
14
|
+
* `name` that submitted `2026/03`. Written once, those are fixed on all eight combinations.
|
|
8
15
|
*/
|
|
9
16
|
export declare function DatePicker(props: DatePickerProp): React.JSX.Element;
|