@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.
Files changed (141) hide show
  1. package/dist/app/app-provider.js +2 -1
  2. package/dist/components/data-display/badge.d.ts +28 -1
  3. package/dist/components/data-display/badge.js +2 -0
  4. package/dist/components/data-display/chat-bubble.d.ts +30 -0
  5. package/dist/components/data-display/chat-bubble.js +229 -0
  6. package/dist/components/data-display/data-table.d.ts +18 -2
  7. package/dist/components/data-display/data-table.js +4 -0
  8. package/dist/components/data-display/descriptions.js +4 -1
  9. package/dist/components/data-display/index.d.ts +5 -3
  10. package/dist/components/data-display/index.js +5 -2
  11. package/dist/components/data-display/popover.d.ts +19 -1
  12. package/dist/components/data-display/popover.js +12 -3
  13. package/dist/components/data-display/progress.d.ts +37 -0
  14. package/dist/components/data-display/progress.js +57 -5
  15. package/dist/components/data-display/scroll-area.js +26 -1
  16. package/dist/components/data-display/tree.d.ts +8 -0
  17. package/dist/components/data-display/tree.js +426 -0
  18. package/dist/components/data-entry/chat-composer.d.ts +50 -0
  19. package/dist/components/data-entry/chat-composer.js +163 -0
  20. package/dist/components/data-entry/chat-suggestion.d.ts +28 -0
  21. package/dist/components/data-entry/chat-suggestion.js +285 -0
  22. package/dist/components/data-entry/date-picker.d.ts +10 -3
  23. package/dist/components/data-entry/date-picker.js +503 -238
  24. package/dist/components/data-entry/form-field.d.ts +1 -1
  25. package/dist/components/data-entry/form-field.js +4 -1
  26. package/dist/components/data-entry/form.js +10 -1
  27. package/dist/components/data-entry/index.d.ts +4 -6
  28. package/dist/components/data-entry/index.js +4 -6
  29. package/dist/components/data-entry/label.d.ts +14 -1
  30. package/dist/components/data-entry/label.js +6 -1
  31. package/dist/components/data-entry/radio.d.ts +27 -6
  32. package/dist/components/data-entry/radio.js +88 -43
  33. package/dist/components/data-entry/search-select.js +8 -1
  34. package/dist/components/data-entry/select.js +2 -0
  35. package/dist/components/data-entry/switch.d.ts +6 -3
  36. package/dist/components/data-entry/switch.js +34 -11
  37. package/dist/components/data-entry/textarea.js +3 -1
  38. package/dist/components/data-entry/tree-utils.d.ts +10 -48
  39. package/dist/components/data-entry/tree-utils.js +1 -154
  40. package/dist/components/layout/mobile-shell.d.ts +1 -1
  41. package/dist/components/layout/mobile-shell.js +2 -0
  42. package/dist/components/navigation/menubar.d.ts +10 -3
  43. package/dist/components/navigation/steps.d.ts +1 -1
  44. package/dist/components/navigation/steps.js +3 -1
  45. package/dist/components/navigation/tabs-scroll.d.ts +27 -0
  46. package/dist/components/navigation/tabs-scroll.js +52 -1
  47. package/dist/components/navigation/tabs.d.ts +2 -2
  48. package/dist/components/navigation/tabs.js +88 -23
  49. package/dist/components/ui/index.d.ts +0 -1
  50. package/dist/components/ui/index.js +0 -1
  51. package/dist/components/ui/segmented.d.ts +15 -1
  52. package/dist/components/ui/segmented.js +13 -6
  53. package/dist/i18n/messages/en.json +48 -2
  54. package/dist/i18n/messages/ja.json +46 -2
  55. package/dist/i18n/messages/vi.json +47 -2
  56. package/dist/lib/datetime/picker-format.d.ts +18 -0
  57. package/dist/lib/datetime/picker-format.js +35 -1
  58. package/dist/lib/field-a11y.d.ts +12 -0
  59. package/dist/lib/field-a11y.js +14 -1
  60. package/dist/lib/tree.d.ts +53 -0
  61. package/dist/lib/tree.js +155 -0
  62. package/dist/props/components/data-display.prop.d.ts +194 -1
  63. package/dist/props/components/data-entry.prop.d.ts +307 -99
  64. package/dist/props/components/index.d.ts +1 -1
  65. package/dist/props/components/layout.prop.d.ts +12 -1
  66. package/dist/props/components/navigation.prop.d.ts +76 -1
  67. package/dist/props/registry.d.ts +156 -19
  68. package/dist/props/registry.js +218 -18
  69. package/dist/props/vocabulary/data.prop.d.ts +12 -0
  70. package/dist/props/vocabulary/index.d.ts +2 -2
  71. package/dist/props/vocabulary/layout.prop.d.ts +11 -0
  72. package/dist/styles/badge-layout.css +4 -0
  73. package/dist/styles/card-layout.css +6 -6
  74. package/dist/styles/control.css +22 -6
  75. package/dist/styles/data-display-layout.css +385 -68
  76. package/dist/styles/data-entry-layout.css +64 -0
  77. package/dist/styles/focus-ring.css +4 -2
  78. package/dist/styles/navigation-layout.css +25 -0
  79. package/dist/styles/shell-layout.css +7 -1
  80. package/dist/styles/table-layout.css +34 -0
  81. package/dist/tokens/base.css +3 -0
  82. package/dist/tokens/components/chat-bubble.css +36 -0
  83. package/dist/tokens/components/chat-composer.css +19 -0
  84. package/dist/tokens/components/data-display.css +9 -6
  85. package/dist/tokens/components/descriptions.css +4 -0
  86. package/dist/tokens/components/navigation.css +6 -0
  87. package/dist/tokens/components/segmented.css +0 -2
  88. package/dist/tokens/components/shell.css +3 -0
  89. package/dist/tokens/components/table.css +3 -0
  90. package/dist/tokens/components/tree.css +27 -0
  91. package/dist/tokens/foundation.css +7 -0
  92. package/docs/COMPONENTS.md +14 -14
  93. package/docs/CONSUMER-RULES.md +24 -1
  94. package/docs/FRAME-COVERAGE-LEDGER.md +1 -1
  95. package/docs/FRAME-COVERAGE-REPORT.md +9 -7
  96. package/docs/TOKENS.md +65 -0
  97. package/docs/data-display/badge.tsx +37 -0
  98. package/docs/data-display/chat-bubble.tsx +397 -0
  99. package/docs/data-display/data-table/index.tsx +18 -0
  100. package/docs/data-display/popover.tsx +41 -1
  101. package/docs/data-display/progress.tsx +28 -0
  102. package/docs/data-display/timeline.tsx +46 -0
  103. package/docs/data-display/tree.tsx +394 -0
  104. package/docs/data-entry/calendar.tsx +2 -2
  105. package/docs/data-entry/chat-composer.tsx +464 -0
  106. package/docs/data-entry/chat-suggestion.tsx +301 -0
  107. package/docs/data-entry/date-picker.tsx +143 -3
  108. package/docs/data-entry/form/examples/invoice-form.tsx +2 -2
  109. package/docs/data-entry/form-field/examples/a11y-contract.tsx +3 -5
  110. package/docs/data-entry/form.tsx +4 -4
  111. package/docs/data-entry/number-input.tsx +31 -0
  112. package/docs/data-entry/segmented.tsx +103 -1
  113. package/docs/layout/mobile-shell.tsx +5 -0
  114. package/docs/navigation/tabs.tsx +149 -0
  115. package/docs/roadmap/ai-chat-components.md +207 -0
  116. package/docs/roadmap/antd-parity.md +155 -0
  117. package/docs/roadmap/badge-tag-chip-count.md +172 -0
  118. package/docs/roadmap/list-masonry.md +159 -0
  119. package/docs/roadmap/parity-audit-data-display-feedback.md +567 -0
  120. package/docs/roadmap/parity-audit-data-entry.md +357 -0
  121. package/docs/roadmap/parity-audit-layout-navigation-general.md +464 -0
  122. package/docs/roadmap/parity-backlog.md +78 -0
  123. package/docs/roadmap/tree-components.md +151 -0
  124. package/docs/showcase/acme-portal.tsx +7 -0
  125. package/docs/showcase/table-tree-rows.tsx +4 -4
  126. package/package.json +13 -23
  127. package/scripts/ui-audit.mjs +17 -4
  128. package/dist/components/data-display/tree-list.d.ts +0 -13
  129. package/dist/components/data-display/tree-list.js +0 -26
  130. package/dist/components/data-entry/date-range-picker.d.ts +0 -11
  131. package/dist/components/data-entry/date-range-picker.js +0 -349
  132. package/dist/components/data-entry/month-picker.d.ts +0 -10
  133. package/dist/components/data-entry/month-picker.js +0 -241
  134. package/dist/components/data-entry/month-range-picker.d.ts +0 -10
  135. package/dist/components/data-entry/month-range-picker.js +0 -301
  136. package/dist/components/ui/date-range-picker.d.ts +0 -1
  137. package/dist/components/ui/date-range-picker.js +0 -2
  138. package/docs/data-display/tree-list.tsx +0 -107
  139. package/docs/data-entry/date-range-picker.tsx +0 -175
  140. package/docs/data-entry/month-picker.tsx +0 -101
  141. 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 value as an ISO-8601
6
- * `yyyy-MM-dd` string (the international standard): it is form-submittable (give it a `name`),
7
- * screen-reader friendly, and e2e-testable by simply filling the input.
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;