@funstack/ui 0.1.0-alpha.0 → 0.1.0-alpha.2

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 (242) hide show
  1. package/README.md +1 -1
  2. package/dist/checkbox-group/checkbox-group-context.mjs +24 -0
  3. package/dist/checkbox-group/checkbox-group-context.mjs.map +1 -0
  4. package/dist/checkbox-group/checkbox-group-item.d.mts +55 -0
  5. package/dist/checkbox-group/checkbox-group-item.d.mts.map +1 -0
  6. package/dist/checkbox-group/checkbox-group-item.mjs +90 -0
  7. package/dist/checkbox-group/checkbox-group-item.mjs.map +1 -0
  8. package/dist/checkbox-group/checkbox-group-label.d.mts +21 -0
  9. package/dist/checkbox-group/checkbox-group-label.d.mts.map +1 -0
  10. package/dist/checkbox-group/checkbox-group-label.mjs +30 -0
  11. package/dist/checkbox-group/checkbox-group-label.mjs.map +1 -0
  12. package/dist/checkbox-group/checkbox-group-parent.d.mts +68 -0
  13. package/dist/checkbox-group/checkbox-group-parent.d.mts.map +1 -0
  14. package/dist/checkbox-group/checkbox-group-parent.mjs +145 -0
  15. package/dist/checkbox-group/checkbox-group-parent.mjs.map +1 -0
  16. package/dist/checkbox-group/checkbox-group-root.d.mts +100 -0
  17. package/dist/checkbox-group/checkbox-group-root.d.mts.map +1 -0
  18. package/dist/checkbox-group/checkbox-group-root.mjs +291 -0
  19. package/dist/checkbox-group/checkbox-group-root.mjs.map +1 -0
  20. package/dist/checkbox-group/index.d.mts +23 -0
  21. package/dist/checkbox-group/index.d.mts.map +1 -0
  22. package/dist/checkbox-group/index.mjs +23 -0
  23. package/dist/checkbox-group/index.mjs.map +1 -0
  24. package/dist/combobox/combobox-clear.d.mts +34 -0
  25. package/dist/combobox/combobox-clear.d.mts.map +1 -0
  26. package/dist/combobox/combobox-clear.mjs +58 -0
  27. package/dist/combobox/combobox-clear.mjs.map +1 -0
  28. package/dist/combobox/combobox-context.mjs +23 -0
  29. package/dist/combobox/combobox-context.mjs.map +1 -0
  30. package/dist/combobox/combobox-group.d.mts +72 -0
  31. package/dist/combobox/combobox-group.d.mts.map +1 -0
  32. package/dist/combobox/combobox-group.mjs +104 -0
  33. package/dist/combobox/combobox-group.mjs.map +1 -0
  34. package/dist/combobox/combobox-input.d.mts +40 -0
  35. package/dist/combobox/combobox-input.d.mts.map +1 -0
  36. package/dist/combobox/combobox-input.mjs +88 -0
  37. package/dist/combobox/combobox-input.mjs.map +1 -0
  38. package/dist/combobox/combobox-label.d.mts +22 -0
  39. package/dist/combobox/combobox-label.d.mts.map +1 -0
  40. package/dist/combobox/combobox-label.mjs +32 -0
  41. package/dist/combobox/combobox-label.mjs.map +1 -0
  42. package/dist/combobox/combobox-list.d.mts +37 -0
  43. package/dist/combobox/combobox-list.d.mts.map +1 -0
  44. package/dist/combobox/combobox-list.mjs +70 -0
  45. package/dist/combobox/combobox-list.mjs.map +1 -0
  46. package/dist/combobox/combobox-option.d.mts +45 -0
  47. package/dist/combobox/combobox-option.d.mts.map +1 -0
  48. package/dist/combobox/combobox-option.mjs +88 -0
  49. package/dist/combobox/combobox-option.mjs.map +1 -0
  50. package/dist/combobox/combobox-popup.d.mts +49 -0
  51. package/dist/combobox/combobox-popup.d.mts.map +1 -0
  52. package/dist/combobox/combobox-popup.mjs +86 -0
  53. package/dist/combobox/combobox-popup.mjs.map +1 -0
  54. package/dist/combobox/combobox-root.d.mts +135 -0
  55. package/dist/combobox/combobox-root.d.mts.map +1 -0
  56. package/dist/combobox/combobox-root.mjs +508 -0
  57. package/dist/combobox/combobox-root.mjs.map +1 -0
  58. package/dist/combobox/combobox-trigger.d.mts +36 -0
  59. package/dist/combobox/combobox-trigger.d.mts.map +1 -0
  60. package/dist/combobox/combobox-trigger.mjs +92 -0
  61. package/dist/combobox/combobox-trigger.mjs.map +1 -0
  62. package/dist/combobox/index.d.mts +36 -0
  63. package/dist/combobox/index.d.mts.map +1 -0
  64. package/dist/combobox/index.mjs +36 -0
  65. package/dist/combobox/index.mjs.map +1 -0
  66. package/dist/dialog/dialog-close.d.mts.map +1 -1
  67. package/dist/dialog/dialog-close.mjs +4 -4
  68. package/dist/dialog/dialog-close.mjs.map +1 -1
  69. package/dist/dialog/dialog-context.mjs.map +1 -1
  70. package/dist/dialog/dialog-popup.mjs +1 -0
  71. package/dist/dialog/dialog-popup.mjs.map +1 -1
  72. package/dist/dialog/dialog-root.mjs +9 -1
  73. package/dist/dialog/dialog-root.mjs.map +1 -1
  74. package/dist/index.d.mts +42 -2
  75. package/dist/index.mjs +6 -1
  76. package/dist/internal/anchor-position.d.mts +7 -0
  77. package/dist/internal/anchor-position.d.mts.map +1 -0
  78. package/dist/internal/anchor-position.mjs +16 -0
  79. package/dist/internal/anchor-position.mjs.map +1 -0
  80. package/dist/internal/platform-attributes.d.mts +18 -1
  81. package/dist/internal/platform-attributes.d.mts.map +1 -1
  82. package/dist/internal/sync-presence.mjs +19 -0
  83. package/dist/internal/sync-presence.mjs.map +1 -0
  84. package/dist/internal/types.d.mts +8 -0
  85. package/dist/internal/types.d.mts.map +1 -1
  86. package/dist/internal/use-action-trigger.mjs +58 -0
  87. package/dist/internal/use-action-trigger.mjs.map +1 -0
  88. package/dist/internal/use-controllable-action.mjs +5 -1
  89. package/dist/internal/use-controllable-action.mjs.map +1 -1
  90. package/dist/internal/use-delayed-pair.mjs +40 -0
  91. package/dist/internal/use-delayed-pair.mjs.map +1 -0
  92. package/dist/internal/use-exit-animation.mjs +55 -0
  93. package/dist/internal/use-exit-animation.mjs.map +1 -0
  94. package/dist/internal/use-form-reset.mjs +5 -1
  95. package/dist/internal/use-form-reset.mjs.map +1 -1
  96. package/dist/internal/use-hydrated-mount.mjs +21 -0
  97. package/dist/internal/use-hydrated-mount.mjs.map +1 -0
  98. package/dist/internal/use-native-group-reconciliation.mjs +2 -2
  99. package/dist/internal/use-native-group-reconciliation.mjs.map +1 -1
  100. package/dist/internal/use-native-select-value.mjs +182 -0
  101. package/dist/internal/use-native-select-value.mjs.map +1 -0
  102. package/dist/internal/use-popover-popup.mjs +274 -0
  103. package/dist/internal/use-popover-popup.mjs.map +1 -0
  104. package/dist/internal/use-popover-root.d.mts +2 -0
  105. package/dist/internal/use-popover-root.mjs +112 -0
  106. package/dist/internal/use-popover-root.mjs.map +1 -0
  107. package/dist/internal/use-popover-trigger.mjs +86 -0
  108. package/dist/internal/use-popover-trigger.mjs.map +1 -0
  109. package/dist/menu/index.d.mts +34 -0
  110. package/dist/menu/index.d.mts.map +1 -0
  111. package/dist/menu/index.mjs +34 -0
  112. package/dist/menu/index.mjs.map +1 -0
  113. package/dist/menu/menu-checkbox-item.d.mts +42 -0
  114. package/dist/menu/menu-checkbox-item.d.mts.map +1 -0
  115. package/dist/menu/menu-checkbox-item.mjs +71 -0
  116. package/dist/menu/menu-checkbox-item.mjs.map +1 -0
  117. package/dist/menu/menu-context.d.mts +1 -0
  118. package/dist/menu/menu-context.mjs +28 -0
  119. package/dist/menu/menu-context.mjs.map +1 -0
  120. package/dist/menu/menu-dom.mjs +37 -0
  121. package/dist/menu/menu-dom.mjs.map +1 -0
  122. package/dist/menu/menu-group.d.mts +48 -0
  123. package/dist/menu/menu-group.d.mts.map +1 -0
  124. package/dist/menu/menu-group.mjs +69 -0
  125. package/dist/menu/menu-group.mjs.map +1 -0
  126. package/dist/menu/menu-hover.mjs +29 -0
  127. package/dist/menu/menu-hover.mjs.map +1 -0
  128. package/dist/menu/menu-item.d.mts +45 -0
  129. package/dist/menu/menu-item.d.mts.map +1 -0
  130. package/dist/menu/menu-item.mjs +54 -0
  131. package/dist/menu/menu-item.mjs.map +1 -0
  132. package/dist/menu/menu-popup.d.mts +69 -0
  133. package/dist/menu/menu-popup.d.mts.map +1 -0
  134. package/dist/menu/menu-popup.mjs +207 -0
  135. package/dist/menu/menu-popup.mjs.map +1 -0
  136. package/dist/menu/menu-radio-group.d.mts +37 -0
  137. package/dist/menu/menu-radio-group.d.mts.map +1 -0
  138. package/dist/menu/menu-radio-group.mjs +72 -0
  139. package/dist/menu/menu-radio-group.mjs.map +1 -0
  140. package/dist/menu/menu-radio-item.d.mts +33 -0
  141. package/dist/menu/menu-radio-item.d.mts.map +1 -0
  142. package/dist/menu/menu-radio-item.mjs +62 -0
  143. package/dist/menu/menu-radio-item.mjs.map +1 -0
  144. package/dist/menu/menu-root.d.mts +54 -0
  145. package/dist/menu/menu-root.d.mts.map +1 -0
  146. package/dist/menu/menu-root.mjs +102 -0
  147. package/dist/menu/menu-root.mjs.map +1 -0
  148. package/dist/menu/menu-submenu-trigger.d.mts +31 -0
  149. package/dist/menu/menu-submenu-trigger.d.mts.map +1 -0
  150. package/dist/menu/menu-submenu-trigger.mjs +92 -0
  151. package/dist/menu/menu-submenu-trigger.mjs.map +1 -0
  152. package/dist/menu/menu-trigger.d.mts +36 -0
  153. package/dist/menu/menu-trigger.d.mts.map +1 -0
  154. package/dist/menu/menu-trigger.mjs +81 -0
  155. package/dist/menu/menu-trigger.mjs.map +1 -0
  156. package/dist/menu/use-menu-item.mjs +54 -0
  157. package/dist/menu/use-menu-item.mjs.map +1 -0
  158. package/dist/popover/index.d.mts +28 -0
  159. package/dist/popover/index.d.mts.map +1 -0
  160. package/dist/popover/index.mjs +28 -0
  161. package/dist/popover/index.mjs.map +1 -0
  162. package/dist/popover/popover-close.d.mts +41 -0
  163. package/dist/popover/popover-close.d.mts.map +1 -0
  164. package/dist/popover/popover-close.mjs +90 -0
  165. package/dist/popover/popover-close.mjs.map +1 -0
  166. package/dist/popover/popover-context.mjs +26 -0
  167. package/dist/popover/popover-context.mjs.map +1 -0
  168. package/dist/popover/popover-description.d.mts +22 -0
  169. package/dist/popover/popover-description.d.mts.map +1 -0
  170. package/dist/popover/popover-description.mjs +28 -0
  171. package/dist/popover/popover-description.mjs.map +1 -0
  172. package/dist/popover/popover-popup.d.mts +82 -0
  173. package/dist/popover/popover-popup.d.mts.map +1 -0
  174. package/dist/popover/popover-popup.mjs +93 -0
  175. package/dist/popover/popover-popup.mjs.map +1 -0
  176. package/dist/popover/popover-root.d.mts +64 -0
  177. package/dist/popover/popover-root.d.mts.map +1 -0
  178. package/dist/popover/popover-root.mjs +39 -0
  179. package/dist/popover/popover-root.mjs.map +1 -0
  180. package/dist/popover/popover-title.d.mts +23 -0
  181. package/dist/popover/popover-title.d.mts.map +1 -0
  182. package/dist/popover/popover-title.mjs +28 -0
  183. package/dist/popover/popover-title.mjs.map +1 -0
  184. package/dist/popover/popover-trigger.d.mts +37 -0
  185. package/dist/popover/popover-trigger.d.mts.map +1 -0
  186. package/dist/popover/popover-trigger.mjs +40 -0
  187. package/dist/popover/popover-trigger.mjs.map +1 -0
  188. package/dist/radio-group/radio-group-item.d.mts.map +1 -1
  189. package/dist/radio-group/radio-group-item.mjs +1 -6
  190. package/dist/radio-group/radio-group-item.mjs.map +1 -1
  191. package/dist/select/index.d.mts +30 -0
  192. package/dist/select/index.d.mts.map +1 -0
  193. package/dist/select/index.mjs +30 -0
  194. package/dist/select/index.mjs.map +1 -0
  195. package/dist/select/select-context.mjs +13 -0
  196. package/dist/select/select-context.mjs.map +1 -0
  197. package/dist/select/select-dom.mjs +44 -0
  198. package/dist/select/select-dom.mjs.map +1 -0
  199. package/dist/select/select-group-label.d.mts +22 -0
  200. package/dist/select/select-group-label.d.mts.map +1 -0
  201. package/dist/select/select-group-label.mjs +39 -0
  202. package/dist/select/select-group-label.mjs.map +1 -0
  203. package/dist/select/select-group.d.mts +28 -0
  204. package/dist/select/select-group.d.mts.map +1 -0
  205. package/dist/select/select-group.mjs +45 -0
  206. package/dist/select/select-group.mjs.map +1 -0
  207. package/dist/select/select-option.d.mts +42 -0
  208. package/dist/select/select-option.d.mts.map +1 -0
  209. package/dist/select/select-option.mjs +65 -0
  210. package/dist/select/select-option.mjs.map +1 -0
  211. package/dist/select/select-root.d.mts +66 -0
  212. package/dist/select/select-root.d.mts.map +1 -0
  213. package/dist/select/select-root.mjs +94 -0
  214. package/dist/select/select-root.mjs.map +1 -0
  215. package/dist/select/select-separator.d.mts +20 -0
  216. package/dist/select/select-separator.d.mts.map +1 -0
  217. package/dist/select/select-separator.mjs +30 -0
  218. package/dist/select/select-separator.mjs.map +1 -0
  219. package/dist/select/select-trigger.d.mts +29 -0
  220. package/dist/select/select-trigger.d.mts.map +1 -0
  221. package/dist/select/select-trigger.mjs +48 -0
  222. package/dist/select/select-trigger.mjs.map +1 -0
  223. package/dist/select/select-value.d.mts +31 -0
  224. package/dist/select/select-value.d.mts.map +1 -0
  225. package/dist/select/select-value.mjs +101 -0
  226. package/dist/select/select-value.mjs.map +1 -0
  227. package/dist/tabs/tabs-context.mjs +2 -1
  228. package/dist/tabs/tabs-context.mjs.map +1 -1
  229. package/dist/tabs/tabs-list.d.mts +1 -1
  230. package/dist/tabs/tabs-list.d.mts.map +1 -1
  231. package/dist/tabs/tabs-list.mjs +17 -9
  232. package/dist/tabs/tabs-list.mjs.map +1 -1
  233. package/dist/tabs/tabs-tab.d.mts +1 -1
  234. package/dist/tabs/tabs-tab.d.mts.map +1 -1
  235. package/dist/tabs/tabs-tab.mjs +6 -3
  236. package/dist/tabs/tabs-tab.mjs.map +1 -1
  237. package/dist/tooltip/tooltip-popup.d.mts.map +1 -1
  238. package/dist/tooltip/tooltip-popup.mjs +2 -7
  239. package/dist/tooltip/tooltip-popup.mjs.map +1 -1
  240. package/dist/tooltip/use-tooltip-interaction.mjs +5 -17
  241. package/dist/tooltip/use-tooltip-interaction.mjs.map +1 -1
  242. package/package.json +6 -1
@@ -0,0 +1,508 @@
1
+ "use client";
2
+ import { createActionDetails } from "../internal/action-details.mjs";
3
+ import { useControllableAction } from "../internal/use-controllable-action.mjs";
4
+ import { resolveStateValue } from "../internal/use-render.mjs";
5
+ import { useDevWarning } from "../internal/use-dev-warning.mjs";
6
+ import { useFormReset } from "../internal/use-form-reset.mjs";
7
+ import { usePopoverRoot } from "../internal/use-popover-root.mjs";
8
+ import { ComboboxContext } from "./combobox-context.mjs";
9
+ import { useCallback, useDeferredValue, useEffect, useId, useInsertionEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
10
+ import { jsx, jsxs } from "react/jsx-runtime";
11
+ import { flushSync } from "react-dom";
12
+ //#region src/combobox/combobox-root.tsx
13
+ /** The default announcement (design 6.4; conventions 11: English, always overridable). */
14
+ function defaultComboboxAnnouncement(count) {
15
+ if (count === 0) return "No results";
16
+ if (count === 1) return "1 result";
17
+ return `${count} results`;
18
+ }
19
+ const VALUE_TRANSITION_TYPES = ["fsui-combobox", "fsui-combobox-select"];
20
+ const isDev = process.env.NODE_ENV !== "production";
21
+ function isPopoverOpen(element) {
22
+ if (element === null) return false;
23
+ try {
24
+ return element.matches(":popover-open");
25
+ } catch {
26
+ return false;
27
+ }
28
+ }
29
+ function isThenable(value) {
30
+ return typeof value === "object" && value !== null && typeof value.then === "function";
31
+ }
32
+ /**
33
+ * Owns the two states of a combobox (design/components/combobox.md 2): the selection (`value`,
34
+ * a transition with `useOptimistic`, like every other value) and the text (`inputValue`, the
35
+ * library's **urgent** lane: `inputValueChangeAction` is called synchronously, outside any
36
+ * transition). The results render from `useDeferredValue(inputValue)`; the active option is
37
+ * urgent virtual focus (`aria-activedescendant`); the listbox is a `popover="auto"` opened by the
38
+ * library with the input as `source`. Renders no DOM, except one `<input type="hidden">` when
39
+ * `name` is given.
40
+ */
41
+ function ComboboxRoot(props) {
42
+ const { value, defaultValue, valueChangeAction, inputValue: inputValueProp, defaultInputValue, open, defaultOpen, openChangeAction, allowCustomValue = false, autoHighlight = false, openOnClick = false, loop = false, name, form, disabled = false, children } = props;
43
+ const id = useId();
44
+ const ids = useMemo(() => ({
45
+ input: `${id}-input`,
46
+ label: `${id}-label`,
47
+ list: `${id}-list`,
48
+ optionPrefix: `${id}-option-`
49
+ }), [id]);
50
+ const popover = usePopoverRoot({
51
+ component: "combobox",
52
+ part: "Combobox.Root",
53
+ mode: "library",
54
+ open,
55
+ defaultOpen,
56
+ openChangeAction
57
+ });
58
+ const propsRef = useRef(props);
59
+ const popoverRef = useRef(popover);
60
+ useInsertionEffect(() => {
61
+ propsRef.current = props;
62
+ popoverRef.current = popover;
63
+ });
64
+ const inputRef = useRef(null);
65
+ const listRef = useRef(null);
66
+ const hiddenRef = useRef(null);
67
+ const [internalText, setInternalText] = useState(() => defaultInputValue ?? inputValueProp ?? "");
68
+ const text = inputValueProp ?? internalText;
69
+ const query = useDeferredValue(text);
70
+ const stale = text !== query;
71
+ const textRef = useRef(text);
72
+ useInsertionEffect(() => {
73
+ textRef.current = text;
74
+ });
75
+ const warnedThenable = useRef(false);
76
+ /** Writes the text (urgent) and calls `inputValueChangeAction` synchronously (design 2.2). */
77
+ const writeText = useCallback((next, reason, event) => {
78
+ const latest = propsRef.current;
79
+ if (latest.inputValue === void 0) setInternalText(next);
80
+ textRef.current = next;
81
+ const action = latest.inputValueChangeAction;
82
+ if (action === void 0) return;
83
+ const result = action(next, createActionDetails(event, { reason }));
84
+ if (isDev && !warnedThenable.current && isThenable(result)) {
85
+ warnedThenable.current = true;
86
+ console.error("Combobox.Root: `inputValueChangeAction` returned a promise. It is the urgent lane: called synchronously and never awaited. Put async work in `valueChangeAction`, or derive the results from the deferred `query`.");
87
+ }
88
+ }, []);
89
+ const [labels] = useState(() => {
90
+ const map = /* @__PURE__ */ new Map();
91
+ const initialValue = value !== void 0 ? value : defaultValue ?? null;
92
+ if (initialValue !== null) map.set(initialValue, defaultInputValue ?? inputValueProp ?? "");
93
+ return map;
94
+ });
95
+ const labelFor = useCallback((v) => v === null ? "" : labels.get(v) ?? "", [labels]);
96
+ const dispatchSeq = useRef(0);
97
+ const [dispatchMeta] = useState(() => /* @__PURE__ */ new WeakMap());
98
+ const textRecord = useRef(null);
99
+ const committedRef = useRef(null);
100
+ const runValueAction = async (next, details) => {
101
+ const meta = dispatchMeta.get(details);
102
+ try {
103
+ await valueChangeAction?.(next, details);
104
+ } catch (error) {
105
+ await void 0;
106
+ const record = textRecord.current;
107
+ if (meta !== void 0 && record !== null && record.id === meta.id) {
108
+ textRecord.current = null;
109
+ if (textRef.current === record.text) writeText(labelFor(committedRef.current), "dismiss", void 0);
110
+ }
111
+ throw error;
112
+ }
113
+ const record = textRecord.current;
114
+ if (meta !== void 0 && record !== null && record.id === meta.id) record.settled = true;
115
+ const current = popoverRef.current;
116
+ if (meta !== void 0 && meta.close && meta.id === dispatchSeq.current && current.openGeneration() === meta.generation && (meta.text === void 0 || textRef.current === meta.text)) current.requestClose(details.event, "activate");
117
+ };
118
+ const selection = useControllableAction({
119
+ controlled: value,
120
+ defaultValue,
121
+ initialValue: null,
122
+ action: runValueAction,
123
+ transitionTypes: () => VALUE_TRANSITION_TYPES,
124
+ warn: {
125
+ component: "Combobox.Root",
126
+ prop: "value"
127
+ }
128
+ });
129
+ useInsertionEffect(() => {
130
+ committedRef.current = selection.committed;
131
+ });
132
+ const dispatchValue = (next, event, fields, options) => {
133
+ const details = createActionDetails(event, fields);
134
+ const dispatchId = ++dispatchSeq.current;
135
+ dispatchMeta.set(details, {
136
+ id: dispatchId,
137
+ generation: popover.openGeneration(),
138
+ close: options.close,
139
+ text: options.text
140
+ });
141
+ textRecord.current = options.text === void 0 ? null : {
142
+ id: dispatchId,
143
+ value: next,
144
+ text: options.text,
145
+ settled: false
146
+ };
147
+ selection.dispatch(next, details);
148
+ };
149
+ const [registry] = useState(() => /* @__PURE__ */ new Map());
150
+ const [optionCount, setOptionCount] = useState(0);
151
+ const [statusCount, setStatusCount] = useState(0);
152
+ const statusCountRef = useRef(0);
153
+ const [labelCount, setLabelCount] = useState(0);
154
+ const warnedDuplicate = useRef(false);
155
+ const registerOption = useCallback((optionValue, entry) => {
156
+ const existing = registry.get(optionValue);
157
+ if (isDev && !warnedDuplicate.current && existing !== void 0 && existing.element !== entry.element && existing.element.isConnected) {
158
+ warnedDuplicate.current = true;
159
+ console.error(`Combobox.Option: two options have the value ${JSON.stringify(optionValue)}. Option ids are derived from the value, so they must be unique within one Combobox.`);
160
+ }
161
+ registry.set(optionValue, entry);
162
+ labels.set(optionValue, entry.label ?? (entry.element.textContent ?? "").trim());
163
+ setOptionCount(registry.size);
164
+ return () => {
165
+ if (registry.get(optionValue) === entry) registry.delete(optionValue);
166
+ setOptionCount(registry.size);
167
+ };
168
+ }, [registry, labels]);
169
+ const registerStatus = useCallback(() => {
170
+ statusCountRef.current += 1;
171
+ setStatusCount((count) => count + 1);
172
+ return () => {
173
+ statusCountRef.current -= 1;
174
+ setStatusCount((count) => count - 1);
175
+ };
176
+ }, []);
177
+ const registerLabel = useCallback(() => {
178
+ setLabelCount((count) => count + 1);
179
+ return () => setLabelCount((count) => count - 1);
180
+ }, []);
181
+ const [activeValue, setActiveValue] = useState(null);
182
+ const activeRef = useRef(null);
183
+ const setActive = useCallback((next) => {
184
+ activeRef.current = next;
185
+ setActiveValue(next);
186
+ }, []);
187
+ const pendingEdge = useRef(null);
188
+ const autoArmed = useRef(true);
189
+ const orderedOptions = useCallback(() => {
190
+ const options = [];
191
+ for (const [optionValue, entry] of registry) options.push({
192
+ value: optionValue,
193
+ ...entry
194
+ });
195
+ options.sort((a, b) => a.element.compareDocumentPosition(b.element) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1);
196
+ return options;
197
+ }, [registry]);
198
+ const loading = statusCount > 0;
199
+ const empty = optionCount === 0 && !stale && !loading;
200
+ useLayoutEffect(() => {
201
+ const current = activeRef.current;
202
+ if (!popover.committed) {
203
+ if (!popover.intended()) pendingEdge.current = null;
204
+ autoArmed.current = true;
205
+ if (current !== null) setActive(null);
206
+ return;
207
+ }
208
+ const enabled = orderedOptions().filter((option) => !option.disabled);
209
+ let next = current;
210
+ if (pendingEdge.current !== null && enabled.length > 0) {
211
+ next = (pendingEdge.current === "first" ? enabled[0] : enabled.at(-1)).value;
212
+ pendingEdge.current = null;
213
+ } else if (next !== null && !enabled.some((option) => option.value === next)) next = null;
214
+ if (next !== null) autoArmed.current = false;
215
+ else if (autoHighlight && autoArmed.current && !stale && enabled.length > 0) {
216
+ next = enabled[0].value;
217
+ autoArmed.current = false;
218
+ }
219
+ if (next !== current) setActive(next);
220
+ });
221
+ const prevCommitted = useRef(selection.committed);
222
+ useLayoutEffect(() => {
223
+ const record = textRecord.current;
224
+ if (record !== null && record.settled && !selection.pending && selection.latestInFlight() === null) {
225
+ textRecord.current = null;
226
+ if (selection.committed !== record.value && textRef.current === record.text) writeText(labelFor(selection.committed), "dismiss", void 0);
227
+ }
228
+ if (prevCommitted.current !== selection.committed) {
229
+ prevCommitted.current = selection.committed;
230
+ const committed = selection.committed;
231
+ if (committed !== null && textRecord.current === null && !labels.has(committed)) labels.set(committed, textRef.current);
232
+ }
233
+ });
234
+ const state = {
235
+ value: selection.optimistic,
236
+ inputValue: text,
237
+ query,
238
+ stale,
239
+ activeValue,
240
+ open: popover.optimistic,
241
+ pending: selection.pending || popover.pending,
242
+ disabled
243
+ };
244
+ const lastAnnounced = useRef(null);
245
+ useEffect(() => {
246
+ if (!popover.committed) {
247
+ lastAnnounced.current = null;
248
+ return;
249
+ }
250
+ if (stale || statusCountRef.current > 0) return;
251
+ const count = registry.size;
252
+ const key = `${count}\u0000${query}`;
253
+ if (lastAnnounced.current === key) return;
254
+ lastAnnounced.current = key;
255
+ const list = listRef.current;
256
+ if (list === null || typeof list.ariaNotify !== "function") return;
257
+ const message = (propsRef.current.announce ?? defaultComboboxAnnouncement)(count, state);
258
+ if (message !== null) list.ariaNotify(message);
259
+ });
260
+ const [mountValue] = useState(value);
261
+ const [mountText] = useState(inputValueProp);
262
+ const valueResetTarget = value !== void 0 ? mountValue ?? null : defaultValue ?? null;
263
+ const inputDefaultValue = inputValueProp !== void 0 ? mountText ?? "" : defaultInputValue ?? "";
264
+ const [resync, setResync] = useState(0);
265
+ useLayoutEffect(() => {
266
+ const input = inputRef.current;
267
+ if (resync > 0 && input !== null && input.value !== textRef.current) input.value = textRef.current;
268
+ }, [resync]);
269
+ const formElement = useMemo(() => ({
270
+ get current() {
271
+ return hiddenRef.current ?? inputRef.current;
272
+ },
273
+ set current(_ignored) {}
274
+ }), []);
275
+ useFormReset(formElement, (event) => {
276
+ const domText = inputRef.current?.value ?? inputDefaultValue;
277
+ if (domText !== textRef.current) writeText(domText, "platform", event);
278
+ setResync((n) => n + 1);
279
+ if (valueResetTarget !== selection.optimistic) dispatchValue(valueResetTarget, event, {
280
+ reason: "platform",
281
+ custom: false
282
+ }, {
283
+ close: false,
284
+ text: void 0
285
+ });
286
+ }, () => {
287
+ const input = inputRef.current;
288
+ if (input !== null && input.defaultValue !== inputDefaultValue) input.defaultValue = inputDefaultValue;
289
+ });
290
+ const reconciled = useRef(false);
291
+ useLayoutEffect(() => {
292
+ if (reconciled.current) return;
293
+ reconciled.current = true;
294
+ const input = inputRef.current;
295
+ if (input !== null && input.value !== textRef.current) writeText(input.value, "platform", void 0);
296
+ }, [writeText]);
297
+ const [missingText] = useState(() => (value ?? defaultValue ?? null) !== null && inputValueProp === void 0 && defaultInputValue === void 0);
298
+ useDevWarning(missingText, "Combobox.Root: `value` / `defaultValue` is set but neither `inputValue` nor `defaultInputValue` is: the input will show no text for the selection. The library never derives the text from the value; pass the selected option's label as well.");
299
+ const accept = (optionValue, event) => {
300
+ if (disabled) return;
301
+ const entry = registry.get(optionValue);
302
+ if (entry === void 0 || entry.disabled) return;
303
+ const label = entry.label ?? (entry.element.textContent ?? "").trim();
304
+ labels.set(optionValue, label);
305
+ pendingEdge.current = null;
306
+ if (activeRef.current !== optionValue) setActive(optionValue);
307
+ writeText(label, "activate", event);
308
+ if (optionValue === selection.optimistic) {
309
+ popover.requestClose(event, "activate");
310
+ return;
311
+ }
312
+ dispatchValue(optionValue, event, {
313
+ reason: "activate",
314
+ custom: false
315
+ }, {
316
+ close: true,
317
+ text: label
318
+ });
319
+ };
320
+ /** Free text as the value (design 5.3). Returns whether it dispatched. */
321
+ const commitText = (event, close) => {
322
+ const current = selection.optimistic;
323
+ const typed = textRef.current;
324
+ if (typed === labelFor(current)) return false;
325
+ const next = typed === "" ? null : typed;
326
+ if (next === current) return false;
327
+ if (next !== null) labels.set(next, typed);
328
+ dispatchValue(next, event, {
329
+ reason: "activate",
330
+ custom: next !== null
331
+ }, {
332
+ close,
333
+ text: typed
334
+ });
335
+ return true;
336
+ };
337
+ const clear = (event) => {
338
+ if (disabled) return;
339
+ if (textRef.current !== "") writeText("", "activate", event);
340
+ if (selection.optimistic !== null) dispatchValue(null, event, {
341
+ reason: "activate",
342
+ custom: false
343
+ }, {
344
+ close: false,
345
+ text: ""
346
+ });
347
+ inputRef.current?.focus();
348
+ };
349
+ const typed = (next, event) => {
350
+ if (disabled) return;
351
+ writeText(next, "activate", event);
352
+ pendingEdge.current = null;
353
+ if (activeRef.current !== null) setActive(null);
354
+ autoArmed.current = true;
355
+ popover.requestOpen(event, "activate");
356
+ };
357
+ const move = (direction) => {
358
+ const enabled = orderedOptions().filter((option) => !option.disabled);
359
+ if (enabled.length === 0) return;
360
+ const last = enabled.length - 1;
361
+ const index = enabled.findIndex((option) => option.value === activeRef.current);
362
+ let next;
363
+ if (direction === 1) next = index < 0 ? 0 : index >= last ? loop ? 0 : last : index + 1;
364
+ else next = index < 0 ? last : index <= 0 ? loop ? last : 0 : index - 1;
365
+ const target = enabled[next];
366
+ setActive(target.value);
367
+ if (typeof target.element.scrollIntoView === "function") target.element.scrollIntoView({ block: "nearest" });
368
+ };
369
+ const activeEnabled = () => {
370
+ const current = activeRef.current;
371
+ if (current === null) return null;
372
+ const entry = registry.get(current);
373
+ return entry !== void 0 && !entry.disabled ? current : null;
374
+ };
375
+ const keyDown = (event) => {
376
+ if (disabled || event.ctrlKey || event.metaKey) return false;
377
+ if (event.isComposing || event.keyCode === 229) return false;
378
+ const isOpen = isPopoverOpen(popover.popupRef.current);
379
+ switch (event.key) {
380
+ case "ArrowDown":
381
+ event.preventDefault();
382
+ if (event.altKey) {
383
+ if (!isOpen) popover.requestOpen(event, "activate");
384
+ } else if (!isOpen) {
385
+ pendingEdge.current = "first";
386
+ popover.requestOpen(event, "activate");
387
+ } else move(1);
388
+ return true;
389
+ case "ArrowUp":
390
+ event.preventDefault();
391
+ if (event.altKey) {
392
+ if (isOpen) {
393
+ const active = activeEnabled();
394
+ if (active !== null) accept(active, event);
395
+ else popover.requestClose(event, "activate");
396
+ }
397
+ } else if (!isOpen) {
398
+ pendingEdge.current = "last";
399
+ popover.requestOpen(event, "activate");
400
+ } else move(-1);
401
+ return true;
402
+ case "Enter":
403
+ if (isOpen) {
404
+ event.preventDefault();
405
+ const active = activeEnabled();
406
+ if (active !== null) accept(active, event);
407
+ else if (allowCustomValue && !commitText(event, true)) popover.requestClose(event, "activate");
408
+ return true;
409
+ }
410
+ if (allowCustomValue) flushSync(() => commitText(event, false));
411
+ return false;
412
+ case "Home":
413
+ case "End":
414
+ case "ArrowLeft":
415
+ case "ArrowRight":
416
+ if (activeRef.current !== null) setActive(null);
417
+ return false;
418
+ default: return false;
419
+ }
420
+ };
421
+ /** Focus has left the input and the popup (design 5.4). */
422
+ const leave = (event) => {
423
+ if (disabled) return;
424
+ const popup = popover.popupRef.current;
425
+ if (popup !== null && isPopoverOpen(popup)) {
426
+ popover.recordCloseIntent({
427
+ reason: "dismiss",
428
+ event
429
+ });
430
+ popup.hidePopover();
431
+ }
432
+ pendingEdge.current = null;
433
+ if (allowCustomValue) {
434
+ commitText(event, false);
435
+ return;
436
+ }
437
+ const label = labelFor(selection.optimistic);
438
+ if (textRef.current !== label) writeText(label, "dismiss", event);
439
+ };
440
+ const leaveRef = useRef(leave);
441
+ useInsertionEffect(() => {
442
+ leaveRef.current = leave;
443
+ });
444
+ const focusOut = (event) => {
445
+ const input = inputRef.current;
446
+ const popup = popover.popupRef.current;
447
+ const next = event.relatedTarget;
448
+ if (next instanceof Node) {
449
+ if (next === input || popup !== null && popup.contains(next)) return;
450
+ leave(event);
451
+ return;
452
+ }
453
+ if (input === null) return;
454
+ const doc = input.ownerDocument;
455
+ setTimeout(() => {
456
+ if (!doc.hasFocus()) return;
457
+ const active = doc.activeElement;
458
+ if (active === input || active !== null && popup !== null && popup.contains(active)) return;
459
+ if (input.isConnected) leaveRef.current(event);
460
+ }, 0);
461
+ };
462
+ const inputClick = (event) => {
463
+ if (disabled || !openOnClick) return;
464
+ popover.requestOpen(event, "activate");
465
+ };
466
+ const context = {
467
+ popover,
468
+ ids,
469
+ value: selection.optimistic,
470
+ valuePending: selection.pending,
471
+ pending: state.pending,
472
+ inputValue: text,
473
+ query,
474
+ stale,
475
+ activeValue,
476
+ empty,
477
+ loading,
478
+ hasLabel: labelCount > 0,
479
+ disabled,
480
+ inputRef,
481
+ listRef,
482
+ registerOption,
483
+ registerStatus,
484
+ registerLabel,
485
+ setActive,
486
+ accept,
487
+ clear,
488
+ typed,
489
+ keyDown,
490
+ focusOut,
491
+ inputClick
492
+ };
493
+ return /* @__PURE__ */ jsxs(ComboboxContext, {
494
+ value: context,
495
+ children: [name !== void 0 && /* @__PURE__ */ jsx("input", {
496
+ ref: hiddenRef,
497
+ type: "hidden",
498
+ name,
499
+ form,
500
+ value: selection.optimistic ?? ""
501
+ }), resolveStateValue(children, state)]
502
+ });
503
+ }
504
+ ComboboxRoot.displayName = "ComboboxRoot";
505
+ //#endregion
506
+ export { ComboboxRoot, defaultComboboxAnnouncement };
507
+
508
+ //# sourceMappingURL=combobox-root.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"combobox-root.mjs","names":[],"sources":["../../src/combobox/combobox-root.tsx"],"sourcesContent":["\"use client\";\nimport {\n useCallback,\n useDeferredValue,\n useEffect,\n useId,\n useInsertionEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { flushSync } from \"react-dom\";\nimport type { ReactNode, RefObject } from \"react\";\nimport { createActionDetails } from \"../internal/action-details.js\";\nimport type {\n ActionDetails,\n ChangeAction,\n ChangeReason,\n StateValue,\n UrgentChangeAction,\n} from \"../internal/types.js\";\nimport { useControllableAction } from \"../internal/use-controllable-action.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { useFormReset } from \"../internal/use-form-reset.js\";\nimport type { FormAssociatedElement } from \"../internal/use-form-reset.js\";\nimport { usePopoverRoot } from \"../internal/use-popover-root.js\";\nimport { resolveStateValue } from \"../internal/use-render.js\";\nimport { ComboboxContext } from \"./combobox-context.js\";\nimport type { ComboboxContextValue, ComboboxOptionEntry } from \"./combobox-context.js\";\n\n/** Details passed as the second argument of `Combobox.Root`'s `valueChangeAction`. */\nexport interface ComboboxValueChangeDetails extends ActionDetails {\n /**\n * `\"activate\"`: the user accepted an option (Enter, click; `details.event` is the `keydown` or\n * `click`), committed free text with `allowCustomValue` (Enter: the `keydown`; focus leaving:\n * the `focusout`), or pressed Clear (`click`).\n * `\"platform\"`: a form reset (`details.event` is the `reset` event).\n */\n readonly reason: ChangeReason;\n /** `true` when `next` is free text committed under `allowCustomValue`, not an option's value. */\n readonly custom: boolean;\n}\n\n/** Details passed as the second argument of `Combobox.Root`'s `inputValueChangeAction`. */\nexport interface ComboboxInputValueChangeDetails extends ActionDetails {\n /**\n * `\"activate\"`: typing (the `input` event), an accepted option's label, Clear.\n * `\"dismiss\"`: a revert: focus left with unmatched text, or the selection rolled back.\n * `\"platform\"`: form reset (the `reset` event), or text found in the input at mount\n * (`undefined`).\n */\n readonly reason: ChangeReason;\n}\n\n/** Details passed as the second argument of `Combobox.Root`'s `openChangeAction`. */\nexport interface ComboboxOpenChangeDetails extends ActionDetails {\n /**\n * `\"activate\"`: typing, ArrowDown / ArrowUp / Alt+ArrowDown, the Trigger, a click on the input\n * with `openOnClick`, an accepted option (the close after it), a foreign `showPopover()`.\n * `\"dismiss\"`: Escape, light dismiss, focus leaving the combobox, another top-layer element.\n * `\"platform\"`: the popup was found open on mount (only script can cause it).\n */\n readonly reason: ChangeReason;\n}\n\nexport interface ComboboxRootState {\n /** Optimistic selection. Content rendered from it must not suspend. */\n readonly value: string | null;\n /** The text in the input (urgent). */\n readonly inputValue: string;\n /** The deferred text the mounted results are for. */\n readonly query: string;\n /** `inputValue !== query`: the results shown are for an older text. */\n readonly stale: boolean;\n /** The value of the option `aria-activedescendant` points at, or `null`. */\n readonly activeValue: string | null;\n /** Optimistic open state. */\n readonly open: boolean;\n /** A `valueChangeAction` or `openChangeAction` dispatched through this Combobox is in flight. */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\nexport interface ComboboxRootProps {\n /** Controlled selection. `null`: nothing selected. `undefined`: uncontrolled. */\n value?: string | null;\n /** Initial selection in uncontrolled mode. Default `null`. */\n defaultValue?: string | null;\n /**\n * Runs inside the Root's transition when the user accepts an option, commits free text\n * (`allowCustomValue`), presses Clear, or the form resets. Awaited; the listbox closes in the\n * commit that ends it; a rejection rolls the input text and `data-selected` back and leaves\n * the listbox open. May be a Server Function. A `setState` after your own `await` needs\n * `startTransition`.\n */\n valueChangeAction?: ChangeAction<string | null, ComboboxValueChangeDetails>;\n\n /** Controlled input text. `undefined`: uncontrolled. */\n inputValue?: string;\n /**\n * Initial input text. Default `\"\"`. Pass the selected value's label when `defaultValue` is\n * set: the library never derives the text from the value.\n */\n defaultInputValue?: string;\n /**\n * The urgent lane: called synchronously, outside any Transition, on every change of the text\n * (typing, and the library's own writes: an accepted option's label, a revert, Clear, a form\n * reset, text typed before hydration). Not awaited; nothing is pending for it. A controlled\n * parent updates its state in the body (an urgent update, which is what a controlled text\n * input needs).\n */\n inputValueChangeAction?: UrgentChangeAction<string, ComboboxInputValueChangeDetails>;\n\n /** Controlled open state. `undefined`: uncontrolled. */\n open?: boolean;\n /** Initial open state when uncontrolled. Opens after hydration. */\n defaultOpen?: boolean;\n /**\n * Awaited for opens and for the close after an accepted option (or the Trigger); a\n * notification for dismissals (Escape, a click outside, focus leaving).\n */\n openChangeAction?: ChangeAction<boolean, ComboboxOpenChangeDetails>;\n\n /**\n * Free text is a value: Enter with no active option, or focus leaving with text that is not\n * the selected option's label, dispatches the text with `details.custom: true` (empty text\n * dispatches `null`). Default `false`: unmatched text reverts to the selection's label when\n * focus leaves.\n */\n allowCustomValue?: boolean;\n /**\n * Make the first enabled option active when the results for an open or an edit settle (APG\n * \"automatic selection\"). Stale results are never highlighted. Default `false`.\n */\n autoHighlight?: boolean;\n /** Open the listbox on a pointer click in the input (not on focus). Default `false`. */\n openOnClick?: boolean;\n /** Wrap from the last option to the first and back with the arrow keys. Default `false`. */\n loop?: boolean;\n /**\n * The message `ariaNotify()` announces on the List when the results settle, from the number\n * of mounted options. Default (English): `\"No results\"`, `\"1 result\"`, `\"N results\"`. Override\n * for localisation or a richer message; return `null` to announce nothing. Ignored where\n * `ariaNotify` is missing.\n */\n announce?: (count: number, state: ComboboxRootState) => string | null;\n\n /** Form participation through a hidden input with this name (value `\"\"` for `null`). */\n name?: string;\n /** The `form` attribute of the hidden input. */\n form?: string;\n /** Disables the Input, Trigger and Clear; nothing dispatches for user interaction. */\n disabled?: boolean;\n children?: StateValue<ReactNode, ComboboxRootState>;\n}\n\n/** The default announcement (design 6.4; conventions 11: English, always overridable). */\nexport function defaultComboboxAnnouncement(count: number): string {\n if (count === 0) return \"No results\";\n if (count === 1) return \"1 result\";\n return `${count} results`;\n}\n\nconst VALUE_TRANSITION_TYPES = [\"fsui-combobox\", \"fsui-combobox-select\"] as const;\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\nfunction isPopoverOpen(element: Element | null): boolean {\n if (element === null) return false;\n try {\n return element.matches(\":popover-open\");\n } catch {\n return false;\n }\n}\n\nfunction isThenable(value: unknown): boolean {\n return (\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as { then?: unknown }).then === \"function\"\n );\n}\n\n/** What a value dispatch carries for the Root's own follow-up (by `details` identity). */\ninterface DispatchMeta {\n readonly id: number;\n /** The open this dispatch belongs to (Popover 3.4): a newer open drops the close. */\n readonly generation: number;\n /** Close the listbox once the action settled (an accepted option, Enter on free text). */\n readonly close: boolean;\n /** The text the library wrote with the dispatch: typing since then keeps the listbox open. */\n readonly text: string | undefined;\n}\n\n/** The text the library wrote for a user dispatch, reverted if the value does not land. */\ninterface TextRecord {\n readonly id: number;\n readonly value: string | null;\n readonly text: string;\n settled: boolean;\n}\n\ninterface OrderedOption extends ComboboxOptionEntry {\n readonly value: string;\n}\n\n/**\n * Owns the two states of a combobox (design/components/combobox.md 2): the selection (`value`,\n * a transition with `useOptimistic`, like every other value) and the text (`inputValue`, the\n * library's **urgent** lane: `inputValueChangeAction` is called synchronously, outside any\n * transition). The results render from `useDeferredValue(inputValue)`; the active option is\n * urgent virtual focus (`aria-activedescendant`); the listbox is a `popover=\"auto\"` opened by the\n * library with the input as `source`. Renders no DOM, except one `<input type=\"hidden\">` when\n * `name` is given.\n */\nexport function ComboboxRoot(props: ComboboxRootProps) {\n const {\n value,\n defaultValue,\n valueChangeAction,\n inputValue: inputValueProp,\n defaultInputValue,\n open,\n defaultOpen,\n openChangeAction,\n allowCustomValue = false,\n autoHighlight = false,\n openOnClick = false,\n loop = false,\n name,\n form,\n disabled = false,\n children,\n } = props;\n\n const id = useId();\n const ids = useMemo(\n () => ({\n input: `${id}-input`,\n label: `${id}-label`,\n list: `${id}-list`,\n optionPrefix: `${id}-option-`,\n }),\n [id],\n );\n\n const popover = usePopoverRoot<ComboboxOpenChangeDetails>({\n component: \"combobox\",\n part: \"Combobox.Root\",\n mode: \"library\",\n open,\n defaultOpen,\n openChangeAction,\n });\n\n // --- Refs the handlers read after an `await` or from a timer ------------------------------\n\n const propsRef = useRef(props);\n const popoverRef = useRef(popover);\n useInsertionEffect(() => {\n propsRef.current = props;\n popoverRef.current = popover;\n });\n\n const inputRef = useRef<HTMLInputElement | null>(null);\n const listRef = useRef<HTMLElement | null>(null);\n const hiddenRef = useRef<HTMLInputElement | null>(null);\n\n // --- Text: the urgent lane (design 4) ------------------------------------------------------\n\n const [internalText, setInternalText] = useState(() => defaultInputValue ?? inputValueProp ?? \"\");\n const text = inputValueProp ?? internalText;\n const query = useDeferredValue(text);\n const stale = text !== query;\n const textRef = useRef(text);\n useInsertionEffect(() => {\n textRef.current = text;\n });\n\n const warnedThenable = useRef(false);\n /** Writes the text (urgent) and calls `inputValueChangeAction` synchronously (design 2.2). */\n const writeText = useCallback(\n (next: string, reason: ChangeReason, event: Event | undefined): void => {\n const latest = propsRef.current;\n if (latest.inputValue === undefined) setInternalText(next);\n textRef.current = next;\n const action = latest.inputValueChangeAction;\n if (action === undefined) return;\n const result: unknown = action(next, createActionDetails(event, { reason }));\n if (isDev && !warnedThenable.current && isThenable(result)) {\n warnedThenable.current = true;\n console.error(\n \"Combobox.Root: `inputValueChangeAction` returned a promise. It is the urgent lane: \" +\n \"called synchronously and never awaited. Put async work in `valueChangeAction`, or \" +\n \"derive the results from the deferred `query`.\",\n );\n }\n },\n [],\n );\n\n // --- Labels (design 2.3) -------------------------------------------------------------------\n\n const [labels] = useState(() => {\n const map = new Map<string, string>();\n const initialValue = value !== undefined ? value : (defaultValue ?? null);\n if (initialValue !== null) map.set(initialValue, defaultInputValue ?? inputValueProp ?? \"\");\n return map;\n });\n const labelFor = useCallback(\n (v: string | null): string => (v === null ? \"\" : (labels.get(v) ?? \"\")),\n [labels],\n );\n\n // --- Selection: a transition (design 3.1, 5.2) --------------------------------------------\n\n const dispatchSeq = useRef(0);\n const [dispatchMeta] = useState(() => new WeakMap<object, DispatchMeta>());\n const textRecord = useRef<TextRecord | null>(null);\n const committedRef = useRef<string | null>(null);\n\n const runValueAction = async (\n next: string | null,\n details: ComboboxValueChangeDetails,\n ): Promise<void> => {\n const meta = dispatchMeta.get(details);\n try {\n await valueChangeAction?.(next, details);\n } catch (error) {\n // Walkthrough 4: the text rolls back with the optimistic value. A synchronous throw lands\n // here inside the Root's `startTransition` callback; yield once so the write (and the\n // consumer's `inputValueChangeAction`) is urgent, as every text write (conventions 2.6).\n await undefined;\n const record = textRecord.current;\n if (meta !== undefined && record !== null && record.id === meta.id) {\n textRecord.current = null;\n if (textRef.current === record.text) {\n writeText(labelFor(committedRef.current), \"dismiss\", undefined);\n }\n }\n throw error;\n }\n const record = textRecord.current;\n if (meta !== undefined && record !== null && record.id === meta.id) record.settled = true;\n // Menu decision 3: close in the commit that ends the action, unless a newer dispatch took\n // over, the user dismissed and re-opened meanwhile, or the user is typing a new search.\n const current = popoverRef.current;\n if (\n meta !== undefined &&\n meta.close &&\n meta.id === dispatchSeq.current &&\n current.openGeneration() === meta.generation &&\n (meta.text === undefined || textRef.current === meta.text)\n ) {\n current.requestClose(details.event, \"activate\");\n }\n };\n\n const selection = useControllableAction<string | null, ComboboxValueChangeDetails>({\n controlled: value,\n defaultValue,\n initialValue: null,\n action: runValueAction,\n transitionTypes: () => VALUE_TRANSITION_TYPES,\n warn: { component: \"Combobox.Root\", prop: \"value\" },\n });\n useInsertionEffect(() => {\n committedRef.current = selection.committed;\n });\n\n const dispatchValue = (\n next: string | null,\n event: Event | undefined,\n fields: { reason: ChangeReason; custom: boolean },\n options: { close: boolean; text: string | undefined },\n ): void => {\n const details = createActionDetails(event, fields);\n const dispatchId = ++dispatchSeq.current;\n dispatchMeta.set(details, {\n id: dispatchId,\n generation: popover.openGeneration(),\n close: options.close,\n text: options.text,\n });\n textRecord.current =\n options.text === undefined\n ? null\n : { id: dispatchId, value: next, text: options.text, settled: false };\n selection.dispatch(next, details);\n };\n\n // --- Option registry and the active option (design 2, 6.5) --------------------------------\n\n const [registry] = useState(() => new Map<string, ComboboxOptionEntry>());\n const [optionCount, setOptionCount] = useState(0);\n const [statusCount, setStatusCount] = useState(0);\n // The same count for the announcement effect: the open commit's passive effects can run before\n // the re-render the Status's registration scheduled (the race `registry` avoids for options).\n const statusCountRef = useRef(0);\n const [labelCount, setLabelCount] = useState(0);\n const warnedDuplicate = useRef(false);\n\n const registerOption = useCallback(\n (optionValue: string, entry: ComboboxOptionEntry) => {\n const existing = registry.get(optionValue);\n if (\n isDev &&\n !warnedDuplicate.current &&\n existing !== undefined &&\n existing.element !== entry.element &&\n existing.element.isConnected\n ) {\n warnedDuplicate.current = true;\n console.error(\n `Combobox.Option: two options have the value ${JSON.stringify(optionValue)}. ` +\n \"Option ids are derived from the value, so they must be unique within one Combobox.\",\n );\n }\n registry.set(optionValue, entry);\n labels.set(optionValue, entry.label ?? (entry.element.textContent ?? \"\").trim());\n setOptionCount(registry.size);\n return () => {\n if (registry.get(optionValue) === entry) registry.delete(optionValue);\n setOptionCount(registry.size);\n };\n },\n [registry, labels],\n );\n\n const registerStatus = useCallback(() => {\n statusCountRef.current += 1;\n setStatusCount((count) => count + 1);\n return () => {\n statusCountRef.current -= 1;\n setStatusCount((count) => count - 1);\n };\n }, []);\n\n const registerLabel = useCallback(() => {\n setLabelCount((count) => count + 1);\n return () => setLabelCount((count) => count - 1);\n }, []);\n\n const [activeValue, setActiveValue] = useState<string | null>(null);\n const activeRef = useRef<string | null>(null);\n const setActive = useCallback((next: string | null) => {\n activeRef.current = next;\n setActiveValue(next);\n }, []);\n // The edge to activate once the open has options (ArrowDown / ArrowUp while closed).\n const pendingEdge = useRef<\"first\" | \"last\" | null>(null);\n // `autoHighlight` (APG automatic selection) highlights once per open and once per edit, when\n // the results for it settle. Home / End / Left / Right clear the active option without\n // re-arming it, so the caret keys really return visual focus to the input.\n const autoArmed = useRef(true);\n\n const orderedOptions = useCallback((): OrderedOption[] => {\n const options: OrderedOption[] = [];\n for (const [optionValue, entry] of registry) options.push({ value: optionValue, ...entry });\n options.sort((a, b) =>\n a.element.compareDocumentPosition(b.element) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1,\n );\n return options;\n }, [registry]);\n\n // A Status inside the List is a Suspense fallback: the results are loading (design 6.3).\n const loading = statusCount > 0;\n const empty = optionCount === 0 && !stale && !loading;\n\n // Revalidation (design 6.5): urgent, in a layout effect after every commit; never dispatches.\n useLayoutEffect(() => {\n const current = activeRef.current;\n if (!popover.committed) {\n // Keep the edge of an open that is on its way (the optimistic commit comes first).\n if (!popover.intended()) pendingEdge.current = null;\n autoArmed.current = true;\n if (current !== null) setActive(null);\n return;\n }\n const enabled = orderedOptions().filter((option) => !option.disabled);\n let next = current;\n if (pendingEdge.current !== null && enabled.length > 0) {\n next = (pendingEdge.current === \"first\" ? enabled[0] : enabled.at(-1))!.value;\n pendingEdge.current = null;\n } else if (next !== null && !enabled.some((option) => option.value === next)) {\n next = null;\n }\n if (next !== null) {\n // Something is active (keys, hover): nothing to highlight for these results.\n autoArmed.current = false;\n } else if (autoHighlight && autoArmed.current && !stale && enabled.length > 0) {\n // Only settled results: a stale list's first option is never auto-highlighted, so a fast\n // Enter commits the typed text or does nothing (design L5).\n next = enabled[0]!.value;\n autoArmed.current = false;\n }\n if (next !== current) setActive(next);\n });\n\n // The selection did not land (a controlled parent refused it, or the caught-inside variant of\n // a controlled parent): the text goes back to the committed value's label (design 2.2).\n // Labels for values set from outside are learnt from the text the consumer passed with them.\n const prevCommitted = useRef(selection.committed);\n useLayoutEffect(() => {\n const record = textRecord.current;\n if (\n record !== null &&\n record.settled &&\n !selection.pending &&\n selection.latestInFlight() === null\n ) {\n textRecord.current = null;\n if (selection.committed !== record.value && textRef.current === record.text) {\n writeText(labelFor(selection.committed), \"dismiss\", undefined);\n }\n }\n if (prevCommitted.current !== selection.committed) {\n prevCommitted.current = selection.committed;\n const committed = selection.committed;\n if (committed !== null && textRecord.current === null && !labels.has(committed)) {\n labels.set(committed, textRef.current);\n }\n }\n });\n\n // --- Announcements (design 6.4) -----------------------------------------------------------\n\n const state: ComboboxRootState = {\n value: selection.optimistic,\n inputValue: text,\n query,\n stale,\n activeValue,\n open: popover.optimistic,\n pending: selection.pending || popover.pending,\n disabled,\n };\n\n const lastAnnounced = useRef<string | null>(null);\n useEffect(() => {\n if (!popover.committed) {\n lastAnnounced.current = null;\n return;\n }\n if (stale || statusCountRef.current > 0) return;\n // The registry, not `optionCount`: a commit's passive effects can run before the re-render\n // that the Options' registration scheduled.\n const count = registry.size;\n const key = `${count}\\u0000${query}`;\n if (lastAnnounced.current === key) return;\n lastAnnounced.current = key;\n const list = listRef.current as (HTMLElement & { ariaNotify?: (m: string) => void }) | null;\n if (list === null || typeof list.ariaNotify !== \"function\") return;\n const announce = propsRef.current.announce ?? defaultComboboxAnnouncement;\n const message = announce(count, state);\n if (message !== null) list.ariaNotify(message);\n });\n\n // --- Form: hidden input, reset, mount reconciliation (design 11.1, walkthroughs 10, 11) ----\n\n const [mountValue] = useState(value);\n const [mountText] = useState(inputValueProp);\n // Reset targets (conventions 3.8 rule 6): uncontrolled, the latest default props; controlled,\n // the mount-time values.\n const valueResetTarget = value !== undefined ? (mountValue ?? null) : (defaultValue ?? null);\n const inputDefaultValue =\n inputValueProp !== undefined ? (mountText ?? \"\") : (defaultInputValue ?? \"\");\n // After a form reset a controlled parent may not adopt the text: put React's value back into\n // the DOM (react-dom only does that after its own input events).\n const [resync, setResync] = useState(0);\n useLayoutEffect(() => {\n const input = inputRef.current;\n if (resync > 0 && input !== null && input.value !== textRef.current) {\n input.value = textRef.current;\n }\n }, [resync]);\n\n const formElement = useMemo<RefObject<FormAssociatedElement | null>>(\n () => ({\n get current() {\n return hiddenRef.current ?? inputRef.current;\n },\n set current(_ignored) {},\n }),\n [],\n );\n useFormReset(\n formElement,\n (event) => {\n const input = inputRef.current;\n const domText = input?.value ?? inputDefaultValue;\n if (domText !== textRef.current) writeText(domText, \"platform\", event);\n // A controlled parent that does not adopt the text gets the DOM re-synced.\n setResync((n) => n + 1);\n if (valueResetTarget !== selection.optimistic) {\n dispatchValue(\n valueResetTarget,\n event,\n { reason: \"platform\", custom: false },\n { close: false, text: undefined },\n );\n }\n },\n () => {\n // react-dom writes `defaultValue = value` after every update of a controlled input, so the\n // reset target is written here, right before the browser's reset algorithm reads it.\n const input = inputRef.current;\n if (input !== null && input.defaultValue !== inputDefaultValue) {\n input.defaultValue = inputDefaultValue;\n }\n },\n );\n\n // Walkthrough 11: react-dom keeps a hydrated text input's DOM value and fires no `onChange`.\n const reconciled = useRef(false);\n useLayoutEffect(() => {\n if (reconciled.current) return;\n reconciled.current = true;\n const input = inputRef.current;\n if (input !== null && input.value !== textRef.current) {\n writeText(input.value, \"platform\", undefined);\n }\n }, [writeText]);\n\n const [missingText] = useState(\n () =>\n (value ?? defaultValue ?? null) !== null &&\n inputValueProp === undefined &&\n defaultInputValue === undefined,\n );\n useDevWarning(\n missingText,\n \"Combobox.Root: `value` / `defaultValue` is set but neither `inputValue` nor \" +\n \"`defaultInputValue` is: the input will show no text for the selection. The library never \" +\n \"derives the text from the value; pass the selected option's label as well.\",\n );\n\n // --- Interaction (design 5, 8) -------------------------------------------------------------\n\n const accept = (optionValue: string, event: Event): void => {\n if (disabled) return;\n const entry = registry.get(optionValue);\n if (entry === undefined || entry.disabled) return;\n const label = entry.label ?? (entry.element.textContent ?? \"\").trim();\n labels.set(optionValue, label);\n pendingEdge.current = null;\n if (activeRef.current !== optionValue) setActive(optionValue);\n writeText(label, \"activate\", event);\n // Re-accepting the selected option dispatches nothing and closes (conventions 2.3 item 8).\n if (optionValue === selection.optimistic) {\n popover.requestClose(event, \"activate\");\n return;\n }\n dispatchValue(\n optionValue,\n event,\n { reason: \"activate\", custom: false },\n { close: true, text: label },\n );\n };\n\n /** Free text as the value (design 5.3). Returns whether it dispatched. */\n const commitText = (event: Event, close: boolean): boolean => {\n const current = selection.optimistic;\n const typed = textRef.current;\n if (typed === labelFor(current)) return false;\n const next = typed === \"\" ? null : typed;\n if (next === current) return false;\n if (next !== null) labels.set(next, typed);\n dispatchValue(\n next,\n event,\n { reason: \"activate\", custom: next !== null },\n { close, text: typed },\n );\n return true;\n };\n\n const clear = (event: Event): void => {\n if (disabled) return;\n if (textRef.current !== \"\") writeText(\"\", \"activate\", event);\n if (selection.optimistic !== null) {\n dispatchValue(null, event, { reason: \"activate\", custom: false }, { close: false, text: \"\" });\n }\n inputRef.current?.focus();\n };\n\n const typed = (next: string, event: Event): void => {\n if (disabled) return;\n writeText(next, \"activate\", event);\n pendingEdge.current = null;\n // APG: editing moves visual focus back to the input; `autoHighlight` picks the first option\n // again once the results for the new text settle.\n if (activeRef.current !== null) setActive(null);\n autoArmed.current = true;\n popover.requestOpen(event, \"activate\");\n };\n\n const move = (direction: 1 | -1): void => {\n const enabled = orderedOptions().filter((option) => !option.disabled);\n if (enabled.length === 0) return;\n const last = enabled.length - 1;\n const index = enabled.findIndex((option) => option.value === activeRef.current);\n let next: number;\n if (direction === 1) next = index < 0 ? 0 : index >= last ? (loop ? 0 : last) : index + 1;\n else next = index < 0 ? last : index <= 0 ? (loop ? last : 0) : index - 1;\n const target = enabled[next]!;\n setActive(target.value);\n if (typeof target.element.scrollIntoView === \"function\") {\n target.element.scrollIntoView({ block: \"nearest\" });\n }\n };\n\n const activeEnabled = (): string | null => {\n const current = activeRef.current;\n if (current === null) return null;\n const entry = registry.get(current);\n return entry !== undefined && !entry.disabled ? current : null;\n };\n\n const keyDown = (event: KeyboardEvent): boolean => {\n if (disabled || event.ctrlKey || event.metaKey) return false;\n // IME: the key belongs to the composition (Enter commits it, arrows pick a candidate).\n if (event.isComposing || event.keyCode === 229) return false;\n // What the user sees, not what is intended: right after Escape the platform has hidden the\n // popup but its `toggle` (and the library's notification) is still queued, and an open that\n // is still committing shows nothing yet. Enter must submit and arrows must open in both.\n const isOpen = isPopoverOpen(popover.popupRef.current);\n switch (event.key) {\n case \"ArrowDown\":\n event.preventDefault();\n if (event.altKey) {\n if (!isOpen) popover.requestOpen(event, \"activate\");\n } else if (!isOpen) {\n pendingEdge.current = \"first\";\n popover.requestOpen(event, \"activate\");\n } else {\n move(1);\n }\n return true;\n case \"ArrowUp\":\n event.preventDefault();\n if (event.altKey) {\n if (isOpen) {\n const active = activeEnabled();\n if (active !== null) accept(active, event);\n else popover.requestClose(event, \"activate\");\n }\n } else if (!isOpen) {\n pendingEdge.current = \"last\";\n popover.requestOpen(event, \"activate\");\n } else {\n move(-1);\n }\n return true;\n case \"Enter\": {\n if (isOpen) {\n // An open listbox means the user is choosing: never submit from here.\n event.preventDefault();\n const active = activeEnabled();\n if (active !== null) accept(active, event);\n else if (allowCustomValue && !commitText(event, true)) {\n popover.requestClose(event, \"activate\");\n }\n return true;\n }\n // Closed: not prevented, so a form still submits. The key's default action (implicit\n // submission) must see the optimistic hidden value: Firefox submits before React's\n // scheduled flush of the optimistic render, so it is flushed here (design 5.3).\n if (allowCustomValue) flushSync(() => commitText(event, false));\n return false;\n }\n case \"Home\":\n case \"End\":\n case \"ArrowLeft\":\n case \"ArrowRight\":\n // APG: the caret keys move visual focus to the input (not prevented: the caret moves).\n if (activeRef.current !== null) setActive(null);\n return false;\n default:\n return false;\n }\n };\n\n /** Focus has left the input and the popup (design 5.4). */\n const leave = (event: Event): void => {\n // `disabled`: nothing dispatches (disabling a focused input blurs it).\n if (disabled) return;\n const popup = popover.popupRef.current;\n if (popup !== null && isPopoverOpen(popup)) {\n popover.recordCloseIntent({ reason: \"dismiss\", event });\n popup.hidePopover();\n }\n pendingEdge.current = null;\n if (allowCustomValue) {\n commitText(event, false);\n return;\n }\n const label = labelFor(selection.optimistic);\n if (textRef.current !== label) writeText(label, \"dismiss\", event);\n };\n const leaveRef = useRef(leave);\n useInsertionEffect(() => {\n leaveRef.current = leave;\n });\n\n const focusOut = (event: FocusEvent): void => {\n const input = inputRef.current;\n const popup = popover.popupRef.current;\n const next = event.relatedTarget;\n if (next instanceof Node) {\n if (next === input || (popup !== null && popup.contains(next))) return;\n leave(event);\n return;\n }\n // No related target: the window lost focus (nothing to do), or a click landed on something\n // that takes no focus (the page itself: focus moved to <body>). Decide after the click.\n if (input === null) return;\n const doc = input.ownerDocument;\n setTimeout(() => {\n if (!doc.hasFocus()) return;\n const active = doc.activeElement;\n if (active === input || (active !== null && popup !== null && popup.contains(active))) {\n return;\n }\n if (input.isConnected) leaveRef.current(event);\n }, 0);\n };\n\n const inputClick = (event: MouseEvent): void => {\n if (disabled || !openOnClick) return;\n popover.requestOpen(event, \"activate\");\n };\n\n const context: ComboboxContextValue = {\n popover,\n ids,\n value: selection.optimistic,\n valuePending: selection.pending,\n pending: state.pending,\n inputValue: text,\n query,\n stale,\n activeValue,\n empty,\n loading,\n hasLabel: labelCount > 0,\n disabled,\n inputRef,\n listRef,\n registerOption,\n registerStatus,\n registerLabel,\n setActive,\n accept,\n clear,\n typed,\n keyDown,\n focusOut,\n inputClick,\n };\n\n return (\n <ComboboxContext value={context}>\n {name !== undefined && (\n <input\n ref={hiddenRef}\n type=\"hidden\"\n name={name}\n form={form}\n value={selection.optimistic ?? \"\"}\n />\n )}\n {resolveStateValue(children, state)}\n </ComboboxContext>\n );\n}\n\nComboboxRoot.displayName = \"ComboboxRoot\";\n"],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import { RenderProp, StateValue } from "../internal/types.mjs";
2
+ import { CSSProperties, ComponentPropsWithRef, ReactNode } from "react";
3
+ //#region src/combobox/combobox-trigger.d.ts
4
+ export interface ComboboxTriggerState {
5
+ /** Optimistic open state. */
6
+ readonly open: boolean;
7
+ /** The open (or close) transition is in flight. */
8
+ readonly pending: boolean;
9
+ readonly disabled: boolean;
10
+ }
11
+ export interface ComboboxTriggerProps extends Omit<ComponentPropsWithRef<"button">, "onClick" | "onPointerDown" | "onMouseDown" | "onPointerCancel" | "tabIndex" | "aria-expanded" | "aria-controls" | "disabled" | "className" | "style" | "children"> {
12
+ /** Disables this button; the Root's `disabled` does too. */
13
+ disabled?: boolean;
14
+ className?: StateValue<string | undefined, ComboboxTriggerState>;
15
+ style?: StateValue<CSSProperties | undefined, ComboboxTriggerState>;
16
+ /** Rendered from optimistic state; must not suspend. */
17
+ children?: StateValue<ReactNode, ComboboxTriggerState>;
18
+ /** Should render a `<button>`. */
19
+ render?: RenderProp<ComponentPropsWithRef<"button">, ComboboxTriggerState>;
20
+ }
21
+ /**
22
+ * The optional button that toggles the listbox: `<button type="button" tabindex="-1"
23
+ * aria-expanded aria-controls>` (the APG keeps it out of the Tab sequence; the input is the one
24
+ * stop). A press does not take focus from the input, and a click focuses the input. No
25
+ * `commandfor`: the listbox is anchored to the input, never to this button (design 1).
26
+ *
27
+ * Owned keys: `type` (on a `<button>`), `tabIndex`, `disabled`, `aria-expanded`,
28
+ * `aria-controls` (token-merged), `onClick`, `onPointerDown`, `onPointerCancel`, `onMouseDown`,
29
+ * `data-open`, `data-pending`, `data-disabled`.
30
+ */
31
+ export declare function ComboboxTrigger({ disabled: ownDisabled, className, style, children, render, ...rest }: ComboboxTriggerProps): ReactNode;
32
+ export declare namespace ComboboxTrigger {
33
+ var displayName: string;
34
+ }
35
+ //#endregion
36
+ //# sourceMappingURL=combobox-trigger.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"combobox-trigger.d.mts","names":[],"sources":["../../src/combobox/combobox-trigger.tsx"],"mappings":";;;iBAeiB;;WAEN;;WAEA;WACA;;iBAGM,6BAA6B,KAC5C;;EAcA;EACA,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;;EAE9C,WAAW,WAAW,WAAW;;EAEjC,SAAS,WAAW,iCAAiC;;;;;;;;;;;;wBAqCvC,kBACd,UAAU,aACV,WACA,OACA,UACA,WACG,QACF,uBAAoB;yBAPP"}