@eifi1/ui-kit 0.22.0 → 0.24.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 (208) hide show
  1. package/README.md +42 -25
  2. package/dist/chart.d.ts +8 -7
  3. package/dist/components/amount-input.d.ts +34 -7
  4. package/dist/components/amount-input.js +14 -10
  5. package/dist/components/amount-input.js.map +1 -1
  6. package/dist/components/autocomplete.d.ts +26 -0
  7. package/dist/components/autocomplete.js +164 -137
  8. package/dist/components/autocomplete.js.map +1 -1
  9. package/dist/components/button-group.d.ts +8 -7
  10. package/dist/components/calculator.d.ts +8 -7
  11. package/dist/components/column-mapper.d.ts +235 -0
  12. package/dist/components/column-mapper.js +411 -0
  13. package/dist/components/column-mapper.js.map +1 -0
  14. package/dist/components/combobox-core.d.ts +39 -2
  15. package/dist/components/combobox-core.js +29 -6
  16. package/dist/components/combobox-core.js.map +1 -1
  17. package/dist/components/combobox.d.ts +113 -3
  18. package/dist/components/combobox.js +456 -313
  19. package/dist/components/combobox.js.map +1 -1
  20. package/dist/components/confirm-dialog.d.ts +8 -7
  21. package/dist/components/confirm-dialog.js +7 -8
  22. package/dist/components/confirm-dialog.js.map +1 -1
  23. package/dist/components/copy-button.d.ts +5 -4
  24. package/dist/components/country-select.d.ts +50 -7
  25. package/dist/components/country-select.js +65 -9
  26. package/dist/components/country-select.js.map +1 -1
  27. package/dist/components/danger-confirm.d.ts +8 -7
  28. package/dist/components/danger-confirm.js +64 -7
  29. package/dist/components/danger-confirm.js.map +1 -1
  30. package/dist/components/date-picker.d.ts +41 -0
  31. package/dist/components/date-picker.js +269 -8
  32. package/dist/components/date-picker.js.map +1 -1
  33. package/dist/components/entity-combobox.d.ts +48 -2
  34. package/dist/components/entity-combobox.js +159 -114
  35. package/dist/components/entity-combobox.js.map +1 -1
  36. package/dist/components/facing-pair.d.ts +8 -7
  37. package/dist/components/field-parts.d.ts +116 -6
  38. package/dist/components/field-parts.js +84 -0
  39. package/dist/components/field-parts.js.map +1 -1
  40. package/dist/components/field-strip.d.ts +105 -0
  41. package/dist/components/field-strip.js +46 -0
  42. package/dist/components/field-strip.js.map +1 -0
  43. package/dist/components/file-button.d.ts +8 -7
  44. package/dist/components/file-dropzone.d.ts +8 -7
  45. package/dist/components/form-actions.d.ts +8 -7
  46. package/dist/components/form-actions.js +23 -3
  47. package/dist/components/form-actions.js.map +1 -1
  48. package/dist/components/iban-input.d.ts +8 -7
  49. package/dist/components/icon-picker.d.ts +10 -1
  50. package/dist/components/icon-picker.js +12 -4
  51. package/dist/components/icon-picker.js.map +1 -1
  52. package/dist/components/language-select.d.ts +8 -7
  53. package/dist/components/money-field.d.ts +11 -7
  54. package/dist/components/money-field.js.map +1 -1
  55. package/dist/components/month-picker.js +2 -1
  56. package/dist/components/month-picker.js.map +1 -1
  57. package/dist/components/multi-entity-combobox.d.ts +31 -2
  58. package/dist/components/multi-entity-combobox.js +152 -103
  59. package/dist/components/multi-entity-combobox.js.map +1 -1
  60. package/dist/components/number-field.d.ts +8 -7
  61. package/dist/components/number-input.d.ts +8 -7
  62. package/dist/components/numpad-sheet.d.ts +8 -7
  63. package/dist/components/phone-input.d.ts +8 -7
  64. package/dist/components/reauth-dialog.d.ts +2 -2
  65. package/dist/components/reauth-dialog.js +6 -8
  66. package/dist/components/reauth-dialog.js.map +1 -1
  67. package/dist/components/series-chart.d.ts +8 -7
  68. package/dist/components/settings-fields.d.ts +8 -7
  69. package/dist/components/share-card.d.ts +8 -7
  70. package/dist/components/swatch-picker.d.ts +17 -1
  71. package/dist/components/swatch-picker.js +10 -4
  72. package/dist/components/swatch-picker.js.map +1 -1
  73. package/dist/components/text-link.d.ts +8 -7
  74. package/dist/components/tile-radio.d.ts +20 -0
  75. package/dist/components/tile-radio.js +25 -8
  76. package/dist/components/tile-radio.js.map +1 -1
  77. package/dist/components/time-input.d.ts +8 -7
  78. package/dist/components/toggle-group.js +2 -2
  79. package/dist/components/toggle-group.js.map +1 -1
  80. package/dist/components/trigger-aria.d.ts +3 -0
  81. package/dist/components/trigger-aria.js +3 -1
  82. package/dist/components/trigger-aria.js.map +1 -1
  83. package/dist/components/ui.d.ts +5 -4
  84. package/dist/components/ui.js +60 -28
  85. package/dist/components/ui.js.map +1 -1
  86. package/dist/feedback/feedback-attachment.d.ts +66 -1
  87. package/dist/feedback/feedback-attachment.js +185 -65
  88. package/dist/feedback/feedback-attachment.js.map +1 -1
  89. package/dist/feedback/feedback-dialog.d.ts +66 -1
  90. package/dist/feedback/feedback-dialog.js.map +1 -1
  91. package/dist/feedback/feedback-inbox.d.ts +67 -252
  92. package/dist/feedback/feedback-inbox.js.map +1 -1
  93. package/dist/feedback/feedback-thread.d.ts +66 -287
  94. package/dist/feedback/feedback-thread.js +16 -3
  95. package/dist/feedback/feedback-thread.js.map +1 -1
  96. package/dist/{kit-labels-R8mIAc9N.d.ts → feedback-DOwPu-Il.d.ts} +1273 -30
  97. package/dist/feedback.d.ts +65 -3
  98. package/dist/hooks/use-file-drop.d.ts +8 -7
  99. package/dist/i18n/defaults.d.ts +8 -7
  100. package/dist/i18n/defaults.js +3 -1
  101. package/dist/i18n/defaults.js.map +1 -1
  102. package/dist/i18n/german.d.ts +8 -7
  103. package/dist/i18n/german.js +36 -2
  104. package/dist/i18n/german.js.map +1 -1
  105. package/dist/i18n/kit-labels.d.ts +5 -4
  106. package/dist/i18n/kit-labels.js.map +1 -1
  107. package/dist/i18n/languages.d.ts +8 -7
  108. package/dist/i18n/locales/de-CH.d.ts +8 -7
  109. package/dist/i18n/locales/en.d.ts +8 -7
  110. package/dist/i18n/locales/en.js +7 -0
  111. package/dist/i18n/locales/en.js.map +1 -1
  112. package/dist/i18n/locales/es.d.ts +8 -7
  113. package/dist/i18n/locales/es.js +37 -2
  114. package/dist/i18n/locales/es.js.map +1 -1
  115. package/dist/i18n/locales/fr.d.ts +8 -7
  116. package/dist/i18n/locales/fr.js +36 -2
  117. package/dist/i18n/locales/fr.js.map +1 -1
  118. package/dist/i18n/locales/hu.d.ts +8 -7
  119. package/dist/i18n/locales/hu.js +38 -2
  120. package/dist/i18n/locales/hu.js.map +1 -1
  121. package/dist/i18n/locales/it.d.ts +8 -7
  122. package/dist/i18n/locales/it.js +37 -2
  123. package/dist/i18n/locales/it.js.map +1 -1
  124. package/dist/i18n/locales/zh.d.ts +8 -7
  125. package/dist/i18n/locales/zh.js +34 -2
  126. package/dist/i18n/locales/zh.js.map +1 -1
  127. package/dist/i18n/review.d.ts +8 -7
  128. package/dist/i18n/review.js +20 -1
  129. package/dist/i18n/review.js.map +1 -1
  130. package/dist/index.d.ts +6 -4
  131. package/dist/index.js +16 -0
  132. package/dist/index.js.map +1 -1
  133. package/dist/lib/column-mapping.d.ts +81 -0
  134. package/dist/lib/column-mapping.js +108 -0
  135. package/dist/lib/column-mapping.js.map +1 -0
  136. package/dist/lib/strip-fade.d.ts +2 -2
  137. package/dist/lib/strip-fade.js.map +1 -1
  138. package/dist/lib/table-text.d.ts +109 -1
  139. package/dist/lib/table-text.js +122 -1
  140. package/dist/lib/table-text.js.map +1 -1
  141. package/dist/rhf/fields.d.ts +142 -13
  142. package/dist/rhf/fields.js +185 -3
  143. package/dist/rhf/fields.js.map +1 -1
  144. package/dist/rhf/form.d.ts +8 -7
  145. package/dist/rhf.d.ts +9 -8
  146. package/dist/rhf.js.map +1 -1
  147. package/dist/shell/app-shell.d.ts +8 -7
  148. package/dist/shell/top-bar-brand.d.ts +8 -7
  149. package/dist/shell/topbar-action-menu.d.ts +36 -3
  150. package/dist/shell/topbar-action-menu.js +74 -33
  151. package/dist/shell/topbar-action-menu.js.map +1 -1
  152. package/dist/shell.d.ts +8 -7
  153. package/dist/table-text.d.ts +1 -1
  154. package/dist/wizard/stepper-nav.d.ts +38 -9
  155. package/dist/wizard/stepper-nav.js +4 -0
  156. package/dist/wizard/stepper-nav.js.map +1 -1
  157. package/dist/wizard.d.ts +8 -7
  158. package/package.json +1 -1
  159. package/src/components/amount-input.tsx +50 -14
  160. package/src/components/autocomplete.tsx +188 -116
  161. package/src/components/column-mapper.tsx +768 -0
  162. package/src/components/combobox-core.tsx +82 -9
  163. package/src/components/combobox.tsx +655 -335
  164. package/src/components/confirm-dialog.tsx +12 -8
  165. package/src/components/country-select.tsx +135 -22
  166. package/src/components/danger-confirm.tsx +274 -24
  167. package/src/components/date-picker.tsx +423 -10
  168. package/src/components/entity-combobox.tsx +243 -129
  169. package/src/components/field-parts.tsx +224 -5
  170. package/src/components/field-strip.tsx +149 -0
  171. package/src/components/form-actions.tsx +52 -5
  172. package/src/components/icon-picker.tsx +23 -4
  173. package/src/components/money-field.tsx +3 -0
  174. package/src/components/month-picker.tsx +2 -1
  175. package/src/components/multi-entity-combobox.tsx +221 -121
  176. package/src/components/reauth-dialog.tsx +17 -18
  177. package/src/components/swatch-picker.tsx +29 -5
  178. package/src/components/tile-radio.tsx +74 -13
  179. package/src/components/toggle-group.tsx +2 -2
  180. package/src/components/trigger-aria.ts +5 -0
  181. package/src/components/ui.tsx +114 -33
  182. package/src/feedback/feedback-attachment.tsx +372 -72
  183. package/src/feedback/feedback-dialog.tsx +7 -3
  184. package/src/feedback/feedback-inbox.tsx +3 -2
  185. package/src/feedback/feedback-thread.tsx +112 -9
  186. package/src/i18n/defaults.ts +2 -0
  187. package/src/i18n/german.ts +42 -0
  188. package/src/i18n/kit-labels.tsx +4 -0
  189. package/src/i18n/locales/en.ts +27 -5
  190. package/src/i18n/locales/es.ts +41 -0
  191. package/src/i18n/locales/fr.ts +42 -0
  192. package/src/i18n/locales/hu.ts +37 -0
  193. package/src/i18n/locales/it.ts +41 -0
  194. package/src/i18n/locales/zh.ts +32 -0
  195. package/src/i18n/review.ts +19 -0
  196. package/src/index.ts +18 -0
  197. package/src/lib/column-mapping.ts +234 -0
  198. package/src/lib/strip-fade.ts +2 -2
  199. package/src/lib/table-text.ts +271 -0
  200. package/src/rhf/fields.tsx +421 -6
  201. package/src/rhf.ts +2 -1
  202. package/src/shell/topbar-action-menu.tsx +134 -38
  203. package/src/wizard/stepper-nav.tsx +35 -1
  204. package/dist/components/field-anatomy.d.ts +0 -95
  205. package/dist/components/field-anatomy.js +0 -84
  206. package/dist/components/field-anatomy.js.map +0 -1
  207. package/dist/feedback-attachment-NhUm7Zga.d.ts +0 -367
  208. package/src/components/field-anatomy.tsx +0 -190
@@ -1,8 +1,8 @@
1
1
  "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
3
- import { Fragment, useId, useMemo, useRef, useState } from "react";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { Fragment as Fragment2, useId, useMemo, useRef, useState } from "react";
4
4
  import { createPortal } from "react-dom";
5
- import { ChevronDown, X } from "lucide-react";
5
+ import { ChevronDown, Plus, X } from "lucide-react";
6
6
  import { FIELD_BASE, FIELD_FLOATING_PAD, FIELD_INVALID, PHONE_QUERY } from "./ui.js";
7
7
  import { cn } from "../lib/cn.js";
8
8
  import { useDropdown } from "./dropdown.js";
@@ -13,7 +13,9 @@ import { PickerSheet, SHEET_ROW_CLASS } from "./picker-sheet.js";
13
13
  import {
14
14
  ComboboxFieldLabel,
15
15
  DISABLED_ROW_CLASS,
16
+ endHintRowProps,
16
17
  isOptionEnabled,
18
+ offersCreate,
17
19
  stepEnabled,
18
20
  SUGGESTION_LIST_CLASS,
19
21
  suggestionRowClass,
@@ -21,7 +23,17 @@ import {
21
23
  useComboboxFieldError
22
24
  } from "./combobox-core.js";
23
25
  import { DEFAULT_COMBOBOX_LABELS, useKitLabels } from "../i18n/kit-labels.js";
24
- import { EndHintRow, FieldCaption, LABEL_IN_ROW, StaticLabelRow, useFieldHint } from "./field-parts.js";
26
+ import {
27
+ EndHintRow,
28
+ FieldCaption,
29
+ LABEL_IN_ROW,
30
+ LockedReason,
31
+ StaticLabelRow,
32
+ useFieldHint,
33
+ useLockReason
34
+ } from "./field-parts.js";
35
+ import { hasMessage, mergeDescribedBy } from "./choice-parts.js";
36
+ import { useCommitReason } from "./write-lock.js";
25
37
  const LIST_CLASS = SUGGESTION_LIST_CLASS;
26
38
  function usePrimaryPressOnly() {
27
39
  const auxPress = useRef(false);
@@ -61,36 +73,45 @@ function SuggestionList({
61
73
  );
62
74
  }
63
75
  const rowClass = suggestionRowClass;
64
- function Combobox({
65
- value,
66
- onChange,
67
- options,
68
- label,
69
- id,
70
- placeholder,
71
- className,
72
- groupBy,
73
- maxSuggestions,
74
- searchPlaceholder,
75
- closeLabel,
76
- createLabel,
77
- invalid,
78
- error,
79
- hint,
80
- disabled,
81
- optionAdornment,
82
- autoFocus,
83
- onBlur,
84
- onSubmit,
85
- "aria-label": ariaLabel,
86
- // Off `rest` and onto the <input>, which is the combobox a reader meets: `Field`'s
87
- // render-prop spreads `{ id, aria-describedby, aria-invalid, aria-required }`, and
88
- // on the wrapper div the hint and required state described nothing.
89
- "aria-describedby": ariaDescribedBy,
90
- "aria-invalid": ariaInvalid,
91
- "aria-required": ariaRequired,
92
- ...rest
93
- }) {
76
+ function lockedKeyDown(e) {
77
+ if (e.key === "Enter") e.preventDefault();
78
+ }
79
+ const CREATE_ROW_CLASS = "flex items-center gap-2 font-medium text-[var(--brand)]";
80
+ function Combobox(props) {
81
+ const {
82
+ value,
83
+ onChange,
84
+ options,
85
+ label,
86
+ id,
87
+ placeholder,
88
+ className,
89
+ groupBy,
90
+ maxSuggestions,
91
+ searchPlaceholder,
92
+ closeLabel,
93
+ sheetTitle,
94
+ createLabel,
95
+ invalid,
96
+ error,
97
+ hint,
98
+ disabled,
99
+ disabledReason,
100
+ commit,
101
+ optionAdornment,
102
+ autoFocus,
103
+ onBlur,
104
+ onSubmit,
105
+ ref,
106
+ "aria-label": ariaLabel,
107
+ // Off `rest` and onto the <input>, which is the combobox a reader meets: `Field`'s
108
+ // render-prop spreads `{ id, aria-describedby, aria-invalid, aria-required }`, and
109
+ // on the wrapper div the hint and required state described nothing.
110
+ "aria-describedby": ariaDescribedBy,
111
+ "aria-invalid": ariaInvalid,
112
+ "aria-required": ariaRequired,
113
+ ...rest
114
+ } = props;
94
115
  const hintParts = useFieldHint(hint, ariaDescribedBy);
95
116
  const field = useComboboxFieldError(
96
117
  error,
@@ -105,6 +126,10 @@ function Combobox({
105
126
  const { open, setOpen, wrapperRef, panelRef } = useDropdown({ backCloses: !isPhone });
106
127
  const primaryOnly = usePrimaryPressOnly();
107
128
  const [active, setActive] = useState(-1);
129
+ const lock = useLockReason(commit, disabledReason);
130
+ const locked = lock.locked;
131
+ const inert = locked || Boolean(disabled);
132
+ if (locked && open) setOpen(false);
108
133
  const sheetInputRef = useRef(null);
109
134
  const fieldRef = useRef(null);
110
135
  const [typing, setTyping] = useState(false);
@@ -143,7 +168,8 @@ function Combobox({
143
168
  });
144
169
  return heads;
145
170
  }, [matches, groupBy]);
146
- const commit = (v) => {
171
+ const take = (v) => {
172
+ if (locked) return;
147
173
  onChange(v);
148
174
  setOpen(false);
149
175
  setActive(-1);
@@ -151,6 +177,7 @@ function Combobox({
151
177
  };
152
178
  const activeId = !isPhone && active >= 0 && active < matches.length ? optionId(active) : void 0;
153
179
  useActiveOptionScroll(activeId);
180
+ const withLock = (box) => locked ? /* @__PURE__ */ jsx(LockedReason, { lock, className: "block", children: box }) : box;
154
181
  const typed = value.trim();
155
182
  const createRow = createLabel && typed.length > 0 && !options.some((o) => o.toLowerCase() === typed.toLowerCase()) ? createLabel(typed) : null;
156
183
  return (
@@ -159,103 +186,120 @@ function Combobox({
159
186
  // which is the combobox a reader meets. Spread FIRST, so the field's ARIA and the
160
187
  // handlers carrying live #309 and dev#549 cannot be replaced from outside.
161
188
  /* @__PURE__ */ jsxs("div", { ...rest, ref: wrapperRef, className: cn("relative", className), children: [
162
- label !== void 0 && hintParts.labelHint === void 0 && /* @__PURE__ */ jsx(ComboboxFieldLabel, { htmlFor: fieldId, className: disabled ? "opacity-50" : void 0, children: label }),
163
- /* @__PURE__ */ jsx(EndHintRow, { hint: label === void 0 ? hintParts.labelHint : void 0, children: /* @__PURE__ */ jsxs("div", { ref: fieldRef, className: cn("relative", disabled && "cursor-not-allowed opacity-50"), children: [
164
- /* @__PURE__ */ jsx(
165
- "input",
166
- {
167
- id: fieldId,
168
- value,
169
- placeholder,
170
- "aria-label": ariaLabel,
171
- role: "combobox",
172
- "aria-expanded": open,
173
- "aria-controls": listboxId,
174
- "aria-activedescendant": activeId,
175
- "aria-autocomplete": "list",
176
- "aria-invalid": field.isInvalid || void 0,
177
- "aria-describedby": field.describedBy,
178
- "aria-required": ariaRequired,
179
- autoComplete: "off",
180
- disabled,
181
- autoFocus,
182
- onBlur,
183
- onMouseDown: primaryOnly.onMouseDown,
184
- onFocus: () => {
185
- if (primaryOnly.fromAuxButton()) return;
186
- setOpen(true);
187
- setTyping(false);
188
- if (isPhone) sheetInputRef.current?.focus();
189
- },
190
- onClick: () => {
191
- setOpen(true);
192
- setTyping(false);
193
- },
194
- inputMode: isPhone ? "none" : void 0,
195
- onChange: (e) => {
196
- onChange(e.target.value);
197
- setOpen(true);
198
- setActive(-1);
199
- setTyping(true);
200
- },
201
- onKeyDown: (e) => {
202
- if (e.key === "ArrowDown") {
203
- e.preventDefault();
189
+ label !== void 0 && hintParts.labelHint === void 0 && /* @__PURE__ */ jsx(ComboboxFieldLabel, { htmlFor: fieldId, className: inert ? "opacity-50" : void 0, children: label }),
190
+ /* @__PURE__ */ jsx(EndHintRow, { ...endHintRowProps("hint" in props, label !== void 0, hintParts.labelHint), children: withLock(
191
+ /* The chevron centers against this inner wrapper, which hugs the input.
192
+ The outer div can be taller than the input (as a grid item it
193
+ stretches to the row height, e.g. next to the editor's category cell
194
+ with its split button), which used to drag a top-1/2 chevron down to
195
+ the input's bottom edge (feedback #248).
196
+ Dimmed as a whole when disabled (or locked), the way EntityCombobox dims its
197
+ trigger. FIELD_BASE's grey alone left a disabled picker looking like a
198
+ filled-in one beside the pickers that do dim (the label is not the input's
199
+ `peer`, so it is dimmed by hand above). */
200
+ /* @__PURE__ */ jsxs("div", { ref: fieldRef, className: cn("relative", inert && "cursor-not-allowed opacity-50"), children: [
201
+ /* @__PURE__ */ jsx(
202
+ "input",
203
+ {
204
+ ref,
205
+ id: fieldId,
206
+ value,
207
+ placeholder,
208
+ "aria-label": ariaLabel,
209
+ role: "combobox",
210
+ "aria-expanded": open,
211
+ "aria-controls": listboxId,
212
+ "aria-activedescendant": activeId,
213
+ "aria-autocomplete": "list",
214
+ "aria-invalid": field.isInvalid || void 0,
215
+ "aria-describedby": mergeDescribedBy(field.describedBy, locked && lock.reasonId),
216
+ "aria-required": ariaRequired,
217
+ autoComplete: "off",
218
+ disabled: locked ? void 0 : disabled,
219
+ readOnly: locked || void 0,
220
+ "aria-disabled": locked || void 0,
221
+ autoFocus,
222
+ onBlur,
223
+ onMouseDown: primaryOnly.onMouseDown,
224
+ onFocus: () => {
225
+ if (primaryOnly.fromAuxButton() || locked) return;
204
226
  setOpen(true);
205
- setActive((i) => Math.min(i + 1, matches.length - 1));
206
- } else if (e.key === "ArrowUp") {
207
- e.preventDefault();
208
- setActive((i) => Math.max(i - 1, 0));
209
- } else if (e.key === "Enter") {
210
- if (open && active >= 0 && active < matches.length) {
227
+ setTyping(false);
228
+ if (isPhone) sheetInputRef.current?.focus();
229
+ },
230
+ onClick: () => {
231
+ if (locked) return;
232
+ setOpen(true);
233
+ setTyping(false);
234
+ },
235
+ inputMode: isPhone ? "none" : void 0,
236
+ onChange: (e) => {
237
+ if (locked) return;
238
+ onChange(e.target.value);
239
+ setOpen(true);
240
+ setActive(-1);
241
+ setTyping(true);
242
+ },
243
+ onKeyDown: (e) => {
244
+ if (locked) return lockedKeyDown(e);
245
+ if (e.key === "ArrowDown") {
246
+ e.preventDefault();
247
+ setOpen(true);
248
+ setActive((i) => Math.min(i + 1, matches.length - 1));
249
+ } else if (e.key === "ArrowUp") {
211
250
  e.preventDefault();
212
- commit(matches[active]);
213
- } else {
214
- if (onSubmit) e.preventDefault();
251
+ setActive((i) => Math.max(i - 1, 0));
252
+ } else if (e.key === "Enter") {
253
+ if (open && active >= 0 && active < matches.length) {
254
+ e.preventDefault();
255
+ take(matches[active]);
256
+ } else {
257
+ if (onSubmit) e.preventDefault();
258
+ setOpen(false);
259
+ onSubmit?.();
260
+ }
261
+ } else if (e.key === "Escape") {
262
+ setOpen(false);
263
+ setActive(-1);
264
+ } else if (e.key === "Tab") {
215
265
  setOpen(false);
216
- onSubmit?.();
266
+ setActive(-1);
217
267
  }
218
- } else if (e.key === "Escape") {
219
- setOpen(false);
220
- setActive(-1);
221
- } else if (e.key === "Tab") {
222
- setOpen(false);
223
- setActive(-1);
224
- }
225
- },
226
- className: cn(
227
- FIELD_BASE,
228
- label !== void 0 && FIELD_FLOATING_PAD,
229
- "pe-9",
230
- field.isInvalid && FIELD_INVALID
231
- )
232
- }
233
- ),
234
- /* @__PURE__ */ jsx(
235
- ChevronDown,
236
- {
237
- "aria-hidden": true,
238
- onMouseDown: (e) => {
239
- e.preventDefault();
240
- if (!disabled) setOpen((o) => !o);
241
- },
242
- className: cn(
243
- "absolute end-2.5 top-1/2 size-4 -translate-y-1/2 text-[var(--text-placeholder)]",
244
- // The box above does the dimming; dimming here too would halve it again.
245
- !disabled && "cursor-pointer"
246
- )
247
- }
248
- )
249
- ] }) }),
250
- label !== void 0 && hintParts.labelHint !== void 0 && /* @__PURE__ */ jsx(StaticLabelRow, { hint: hintParts.labelHint, children: /* @__PURE__ */ jsx(ComboboxFieldLabel, { htmlFor: fieldId, className: cn(LABEL_IN_ROW, disabled && "opacity-50"), children: label }) }),
268
+ },
269
+ className: cn(
270
+ FIELD_BASE,
271
+ label !== void 0 && FIELD_FLOATING_PAD,
272
+ "pe-9",
273
+ field.isInvalid && FIELD_INVALID
274
+ )
275
+ }
276
+ ),
277
+ /* @__PURE__ */ jsx(
278
+ ChevronDown,
279
+ {
280
+ "aria-hidden": true,
281
+ onMouseDown: (e) => {
282
+ e.preventDefault();
283
+ if (!inert) setOpen((o) => !o);
284
+ },
285
+ className: cn(
286
+ "absolute end-2.5 top-1/2 size-4 -translate-y-1/2 text-[var(--text-placeholder)]",
287
+ // The box above does the dimming; dimming here too would halve it again.
288
+ !inert && "cursor-pointer"
289
+ )
290
+ }
291
+ )
292
+ ] })
293
+ ) }),
294
+ label !== void 0 && hintParts.labelHint !== void 0 && /* @__PURE__ */ jsx(StaticLabelRow, { hint: hintParts.labelHint, children: /* @__PURE__ */ jsx(ComboboxFieldLabel, { htmlFor: fieldId, className: cn(LABEL_IN_ROW, inert && "opacity-50"), children: label }) }),
251
295
  /* @__PURE__ */ jsx(FieldCaption, { parts: hintParts }),
252
296
  field.errorEl,
253
297
  isPhone && /* @__PURE__ */ jsx(
254
298
  PickerSheet,
255
299
  {
256
- open: open && !disabled,
300
+ open: open && !inert,
257
301
  onClose: () => setOpen(false),
258
- title: label,
302
+ title: sheetTitle ?? label,
259
303
  query: value,
260
304
  onQueryChange: (v) => {
261
305
  onChange(v);
@@ -274,14 +318,14 @@ function Combobox({
274
318
  type: "button",
275
319
  role: "option",
276
320
  "aria-selected": false,
277
- onClick: () => commit(typed),
321
+ onClick: () => take(typed),
278
322
  className: cn(SHEET_ROW_CLASS, "font-medium text-[var(--brand)]"),
279
323
  children: createRow
280
324
  }
281
325
  ) }),
282
326
  matches.map((o, i) => {
283
327
  const group = groupHeadings?.get(i);
284
- return /* @__PURE__ */ jsxs(Fragment, { children: [
328
+ return /* @__PURE__ */ jsxs(Fragment2, { children: [
285
329
  group !== void 0 && /* @__PURE__ */ jsx(
286
330
  "li",
287
331
  {
@@ -296,7 +340,7 @@ function Combobox({
296
340
  type: "button",
297
341
  role: "option",
298
342
  "aria-selected": o === value,
299
- onClick: () => commit(o),
343
+ onClick: () => take(o),
300
344
  className: cn(
301
345
  SHEET_ROW_CLASS,
302
346
  optionAdornment && "flex items-center justify-between gap-2"
@@ -312,7 +356,7 @@ function Combobox({
312
356
  ] })
313
357
  }
314
358
  ),
315
- !isPhone && open && !disabled && (matches.length > 0 || createRow) && /* @__PURE__ */ jsxs(SuggestionList, { id: listboxId, anchorRef: fieldRef, panelRef, children: [
359
+ !isPhone && open && !inert && (matches.length > 0 || createRow) && /* @__PURE__ */ jsxs(SuggestionList, { id: listboxId, anchorRef: fieldRef, panelRef, children: [
316
360
  createRow && /* @__PURE__ */ jsx("li", { role: "presentation", children: /* @__PURE__ */ jsx(
317
361
  "button",
318
362
  {
@@ -322,7 +366,7 @@ function Combobox({
322
366
  tabIndex: -1,
323
367
  onMouseDown: (e) => {
324
368
  e.preventDefault();
325
- commit(typed);
369
+ take(typed);
326
370
  },
327
371
  className: cn(rowClass(false), "font-medium text-[var(--brand)]"),
328
372
  children: createRow
@@ -330,7 +374,7 @@ function Combobox({
330
374
  ) }),
331
375
  matches.map((o, i) => {
332
376
  const group = groupHeadings?.get(i);
333
- return /* @__PURE__ */ jsxs(Fragment, { children: [
377
+ return /* @__PURE__ */ jsxs(Fragment2, { children: [
334
378
  group !== void 0 && /* @__PURE__ */ jsx(
335
379
  "li",
336
380
  {
@@ -349,7 +393,7 @@ function Combobox({
349
393
  tabIndex: -1,
350
394
  onMouseDown: (e) => {
351
395
  e.preventDefault();
352
- commit(o);
396
+ take(o);
353
397
  },
354
398
  onMouseEnter: () => setActive(i),
355
399
  className: cn(
@@ -372,34 +416,43 @@ function Combobox({
372
416
  ] })
373
417
  );
374
418
  }
375
- function InlineEntityCombobox({
376
- value: rawValue,
377
- onChange,
378
- clearValue = null,
379
- options,
380
- label,
381
- id,
382
- placeholder,
383
- className,
384
- disabled,
385
- autoFocus,
386
- searchPlaceholder,
387
- emptyLabel,
388
- closeLabel,
389
- clearable,
390
- clearLabel,
391
- invalid,
392
- error,
393
- hint,
394
- "aria-label": ariaLabel,
395
- // Off `rest` and onto the <input>, which is the combobox a reader meets: `Field`'s
396
- // render-prop spreads `{ id, aria-describedby, aria-invalid, aria-required }`, and
397
- // on the wrapper div the hint and required state described nothing.
398
- "aria-describedby": ariaDescribedBy,
399
- "aria-invalid": ariaInvalid,
400
- "aria-required": ariaRequired,
401
- ...rest
402
- }) {
419
+ function InlineEntityCombobox(props) {
420
+ const {
421
+ value: rawValue,
422
+ onChange,
423
+ clearValue = null,
424
+ options,
425
+ label,
426
+ id,
427
+ placeholder,
428
+ className,
429
+ disabled,
430
+ autoFocus,
431
+ searchPlaceholder,
432
+ emptyLabel,
433
+ closeLabel,
434
+ sheetTitle,
435
+ clearable,
436
+ clearLabel,
437
+ disabledReason,
438
+ commit,
439
+ onCreate,
440
+ createLabel,
441
+ createEmptyLabel,
442
+ createCommit,
443
+ invalid,
444
+ error,
445
+ hint,
446
+ ref,
447
+ "aria-label": ariaLabel,
448
+ // Off `rest` and onto the <input>, which is the combobox a reader meets: `Field`'s
449
+ // render-prop spreads `{ id, aria-describedby, aria-invalid, aria-required }`, and
450
+ // on the wrapper div the hint and required state described nothing.
451
+ "aria-describedby": ariaDescribedBy,
452
+ "aria-invalid": ariaInvalid,
453
+ "aria-required": ariaRequired,
454
+ ...rest
455
+ } = props;
403
456
  const value = rawValue == null || rawValue === clearValue ? null : rawValue;
404
457
  const hintParts = useFieldHint(hint, ariaDescribedBy);
405
458
  const field = useComboboxFieldError(
@@ -422,14 +475,24 @@ function InlineEntityCombobox({
422
475
  const labels = useKitLabels("combobox", DEFAULT_COMBOBOX_LABELS, {
423
476
  search: searchPlaceholder,
424
477
  noResults: emptyLabel,
425
- clear: clearLabel
478
+ clear: clearLabel,
479
+ create: createLabel
426
480
  });
481
+ const lock = useLockReason(commit, disabledReason);
482
+ const locked = lock.locked;
483
+ const inert = locked || Boolean(disabled);
484
+ if (locked && (open || text !== null)) {
485
+ setOpen(false);
486
+ setText(null);
487
+ }
488
+ const createReason = useCommitReason(createCommit, void 0);
489
+ const createLocked = hasMessage(createReason);
427
490
  const selected = useMemo(
428
491
  () => value == null ? null : options.find((o) => o.value === value) ?? null,
429
492
  [options, value]
430
493
  );
431
494
  const shown = text ?? selected?.label ?? "";
432
- const showClear = Boolean(clearable && value != null && !disabled);
495
+ const showClear = Boolean(clearable && value != null && !inert);
433
496
  const typedQuery = text !== null && text !== (selected?.label ?? "") ? text.trim().toLowerCase() : "";
434
497
  const query = isPhone ? sheetQuery.trim().toLowerCase() : typedQuery;
435
498
  const matches = useMemo(() => {
@@ -451,21 +514,37 @@ function InlineEntityCombobox({
451
514
  }
452
515
  return [...blocks.values()].flat();
453
516
  }, [options, query]);
454
- const activeId = !isPhone && isOptionEnabled(matches[active]) ? optionId(active) : void 0;
517
+ const createQuery = isPhone ? sheetQuery.trim() : text !== null && text !== (selected?.label ?? "") ? text.trim() : "";
518
+ const showCreate = offersCreate(
519
+ onCreate,
520
+ createQuery,
521
+ options.map((o) => o.label),
522
+ createEmptyLabel
523
+ );
524
+ const createText = createQuery ? labels.create(createQuery) : createEmptyLabel;
525
+ const rows = showCreate ? [...matches, { disabled: createLocked }] : matches;
526
+ const createIndex = matches.length;
527
+ const activeId = !isPhone && isOptionEnabled(rows[active]) ? optionId(active) : void 0;
455
528
  useActiveOptionScroll(activeId);
456
529
  const close = () => {
457
530
  setOpen(false);
458
531
  setActive(-1);
459
532
  setSheetQuery("");
460
533
  };
461
- const commit = (o) => {
462
- if (o.disabled) return;
534
+ const take = (o) => {
535
+ if (o.disabled || locked) return;
463
536
  if (o.value !== value) onChange(o.value);
464
537
  setText(null);
465
538
  close();
466
539
  };
540
+ const create = () => {
541
+ if (!onCreate || locked || createLocked) return;
542
+ onCreate(createQuery);
543
+ setText(null);
544
+ close();
545
+ };
467
546
  const reconcile = () => {
468
- if (text !== null) {
547
+ if (text !== null && !locked) {
469
548
  const q = text.trim();
470
549
  if (!q) {
471
550
  if (value != null) onChange(clearValue);
@@ -476,126 +555,148 @@ function InlineEntityCombobox({
476
555
  const ids = new Set(hits.map((h) => h.value));
477
556
  if (ids.size === 1 && hits[0].value !== value) onChange(hits[0].value);
478
557
  }
479
- setText(null);
480
558
  }
559
+ setText(null);
481
560
  close();
482
561
  };
562
+ const createRowContent = /* @__PURE__ */ jsxs(Fragment, { children: [
563
+ /* @__PURE__ */ jsx(Plus, { "aria-hidden": true, className: "size-4 shrink-0" }),
564
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
565
+ /* @__PURE__ */ jsx("span", { className: "block truncate", children: createText }),
566
+ createLocked && /* @__PURE__ */ jsx("span", { className: "block truncate text-xs font-normal text-[var(--text-placeholder)]", children: createReason })
567
+ ] })
568
+ ] });
569
+ const withLock = (box) => locked ? /* @__PURE__ */ jsx(LockedReason, { lock, className: "block", children: box }) : box;
483
570
  return (
484
571
  // `rest` dresses the outer box — a `data-tour` anchor, a test id. Not the NAME,
485
572
  // description, invalid or required state: those belong on the <input> below,
486
573
  // which is the combobox a reader meets. Spread FIRST, so the field's ARIA and the
487
574
  // handlers carrying live #309 and dev#549 cannot be replaced from outside.
488
575
  /* @__PURE__ */ jsxs("div", { ...rest, ref: wrapperRef, className: cn("relative", className), children: [
489
- label !== void 0 && hintParts.labelHint === void 0 && /* @__PURE__ */ jsx(ComboboxFieldLabel, { htmlFor: fieldId, className: disabled ? "opacity-50" : void 0, children: label }),
490
- /* @__PURE__ */ jsx(EndHintRow, { hint: label === void 0 ? hintParts.labelHint : void 0, children: /* @__PURE__ */ jsxs("div", { ref: fieldRef, className: cn("relative", disabled && "cursor-not-allowed opacity-50"), children: [
491
- /* @__PURE__ */ jsx(
492
- "input",
493
- {
494
- id: fieldId,
495
- value: shown,
496
- placeholder,
497
- "aria-label": ariaLabel,
498
- role: "combobox",
499
- "aria-expanded": open,
500
- "aria-controls": listboxId,
501
- "aria-activedescendant": activeId,
502
- "aria-autocomplete": "list",
503
- "aria-invalid": field.isInvalid || void 0,
504
- "aria-describedby": field.describedBy,
505
- "aria-required": ariaRequired,
506
- autoComplete: "off",
507
- disabled,
508
- autoFocus,
509
- inputMode: isPhone ? "none" : void 0,
510
- onMouseDown: primaryOnly.onMouseDown,
511
- onFocus: (e) => {
512
- if (primaryOnly.fromAuxButton()) return;
513
- setText(shown);
514
- e.currentTarget.select();
515
- setOpen(true);
516
- if (isPhone) sheetInputRef.current?.focus();
517
- },
518
- onBlur: () => {
519
- if (!isPhone) reconcile();
520
- },
521
- onChange: (e) => {
522
- setText(e.target.value);
523
- setOpen(true);
524
- setActive(-1);
525
- },
526
- onKeyDown: (e) => {
527
- if (e.key === "ArrowDown") {
528
- e.preventDefault();
576
+ label !== void 0 && hintParts.labelHint === void 0 && /* @__PURE__ */ jsx(ComboboxFieldLabel, { htmlFor: fieldId, className: inert ? "opacity-50" : void 0, children: label }),
577
+ /* @__PURE__ */ jsx(EndHintRow, { ...endHintRowProps("hint" in props, label !== void 0, hintParts.labelHint), children: withLock(
578
+ /* Inner wrapper for chevron centering — same reasoning as Combobox above.
579
+ It is also what the portalled list anchors to.
580
+ Dimmed as a whole when disabled (or locked), the way EntityCombobox dims its
581
+ trigger. FIELD_BASE's grey alone left a disabled picker looking like a
582
+ filled-in one beside the pickers that do dim (the label is not the input's
583
+ `peer`, so it is dimmed by hand above). */
584
+ /* @__PURE__ */ jsxs("div", { ref: fieldRef, className: cn("relative", inert && "cursor-not-allowed opacity-50"), children: [
585
+ /* @__PURE__ */ jsx(
586
+ "input",
587
+ {
588
+ ref,
589
+ id: fieldId,
590
+ value: shown,
591
+ placeholder,
592
+ "aria-label": ariaLabel,
593
+ role: "combobox",
594
+ "aria-expanded": open,
595
+ "aria-controls": listboxId,
596
+ "aria-activedescendant": activeId,
597
+ "aria-autocomplete": "list",
598
+ "aria-invalid": field.isInvalid || void 0,
599
+ "aria-describedby": mergeDescribedBy(field.describedBy, locked && lock.reasonId),
600
+ "aria-required": ariaRequired,
601
+ autoComplete: "off",
602
+ disabled: locked ? void 0 : disabled,
603
+ readOnly: locked || void 0,
604
+ "aria-disabled": locked || void 0,
605
+ autoFocus,
606
+ inputMode: isPhone ? "none" : void 0,
607
+ onMouseDown: primaryOnly.onMouseDown,
608
+ onFocus: (e) => {
609
+ if (primaryOnly.fromAuxButton() || locked) return;
610
+ setText(shown);
611
+ e.currentTarget.select();
529
612
  setOpen(true);
530
- setActive((i) => stepEnabled(matches, i, 1));
531
- } else if (e.key === "ArrowUp") {
532
- e.preventDefault();
533
- setActive((i) => i < 0 ? stepEnabled(matches, -1, 1) : stepEnabled(matches, i, -1));
534
- } else if (e.key === "Enter") {
535
- if (open && isOptionEnabled(matches[active])) {
613
+ if (isPhone) sheetInputRef.current?.focus();
614
+ },
615
+ onBlur: () => {
616
+ if (!isPhone) reconcile();
617
+ },
618
+ onChange: (e) => {
619
+ if (locked) return;
620
+ setText(e.target.value);
621
+ setOpen(true);
622
+ setActive(-1);
623
+ },
624
+ onKeyDown: (e) => {
625
+ if (locked) return lockedKeyDown(e);
626
+ if (e.key === "ArrowDown") {
627
+ e.preventDefault();
628
+ setOpen(true);
629
+ setActive((i) => stepEnabled(rows, i, 1));
630
+ } else if (e.key === "ArrowUp") {
536
631
  e.preventDefault();
537
- commit(matches[active]);
538
- } else {
632
+ setActive((i) => i < 0 ? stepEnabled(rows, -1, 1) : stepEnabled(rows, i, -1));
633
+ } else if (e.key === "Enter") {
634
+ if (open && isOptionEnabled(rows[active])) {
635
+ e.preventDefault();
636
+ if (active === createIndex && showCreate) create();
637
+ else take(matches[active]);
638
+ } else {
639
+ reconcile();
640
+ }
641
+ } else if (e.key === "Escape") {
642
+ setText(null);
643
+ close();
644
+ } else if (e.key === "Tab") {
539
645
  reconcile();
540
646
  }
541
- } else if (e.key === "Escape") {
647
+ },
648
+ className: cn(
649
+ FIELD_BASE,
650
+ label !== void 0 && FIELD_FLOATING_PAD,
651
+ "pe-9",
652
+ field.isInvalid && FIELD_INVALID
653
+ )
654
+ }
655
+ ),
656
+ showClear ? /* @__PURE__ */ jsx(
657
+ "button",
658
+ {
659
+ type: "button",
660
+ tabIndex: -1,
661
+ "aria-label": labels.clear,
662
+ onMouseDown: (e) => e.preventDefault(),
663
+ onClick: () => {
542
664
  setText(null);
543
665
  close();
544
- } else if (e.key === "Tab") {
545
- reconcile();
546
- }
547
- },
548
- className: cn(
549
- FIELD_BASE,
550
- label !== void 0 && FIELD_FLOATING_PAD,
551
- "pe-9",
552
- field.isInvalid && FIELD_INVALID
553
- )
554
- }
555
- ),
556
- showClear ? /* @__PURE__ */ jsx(
557
- "button",
558
- {
559
- type: "button",
560
- tabIndex: -1,
561
- "aria-label": labels.clear,
562
- onMouseDown: (e) => e.preventDefault(),
563
- onClick: () => {
564
- setText(null);
565
- close();
566
- onChange(clearValue);
567
- },
568
- className: cn(
569
- "absolute end-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-[var(--text-placeholder)]",
570
- "hover:text-[var(--text-secondary)]"
571
- ),
572
- children: /* @__PURE__ */ jsx(X, { "aria-hidden": true, className: "size-4" })
573
- }
574
- ) : /* @__PURE__ */ jsx(
575
- ChevronDown,
576
- {
577
- "aria-hidden": true,
578
- onMouseDown: (e) => {
579
- e.preventDefault();
580
- if (!disabled) setOpen((o) => !o);
581
- },
582
- className: cn(
583
- "absolute end-2.5 top-1/2 size-4 -translate-y-1/2 text-[var(--text-placeholder)]",
584
- // The box above does the dimming; dimming here too would halve it again.
585
- !disabled && "cursor-pointer"
586
- )
587
- }
588
- )
589
- ] }) }),
590
- label !== void 0 && hintParts.labelHint !== void 0 && /* @__PURE__ */ jsx(StaticLabelRow, { hint: hintParts.labelHint, children: /* @__PURE__ */ jsx(ComboboxFieldLabel, { htmlFor: fieldId, className: cn(LABEL_IN_ROW, disabled && "opacity-50"), children: label }) }),
666
+ if (!locked) onChange(clearValue);
667
+ },
668
+ className: cn(
669
+ "absolute end-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-[var(--text-placeholder)]",
670
+ "hover:text-[var(--text-secondary)]"
671
+ ),
672
+ children: /* @__PURE__ */ jsx(X, { "aria-hidden": true, className: "size-4" })
673
+ }
674
+ ) : /* @__PURE__ */ jsx(
675
+ ChevronDown,
676
+ {
677
+ "aria-hidden": true,
678
+ onMouseDown: (e) => {
679
+ e.preventDefault();
680
+ if (!inert) setOpen((o) => !o);
681
+ },
682
+ className: cn(
683
+ "absolute end-2.5 top-1/2 size-4 -translate-y-1/2 text-[var(--text-placeholder)]",
684
+ // The box above does the dimming; dimming here too would halve it again.
685
+ !inert && "cursor-pointer"
686
+ )
687
+ }
688
+ )
689
+ ] })
690
+ ) }),
691
+ label !== void 0 && hintParts.labelHint !== void 0 && /* @__PURE__ */ jsx(StaticLabelRow, { hint: hintParts.labelHint, children: /* @__PURE__ */ jsx(ComboboxFieldLabel, { htmlFor: fieldId, className: cn(LABEL_IN_ROW, inert && "opacity-50"), children: label }) }),
591
692
  /* @__PURE__ */ jsx(FieldCaption, { parts: hintParts }),
592
693
  field.errorEl,
593
694
  isPhone && /* @__PURE__ */ jsx(
594
695
  PickerSheet,
595
696
  {
596
- open: open && !disabled,
697
+ open: open && !inert,
597
698
  onClose: reconcile,
598
- title: label,
699
+ title: sheetTitle ?? label,
599
700
  query: sheetQuery,
600
701
  onQueryChange: (v) => {
601
702
  setSheetQuery(v);
@@ -609,7 +710,7 @@ function InlineEntityCombobox({
609
710
  // Keyed by group AND value, like combobox-core.tsx: an option may
610
711
  // deliberately appear twice (see `reconcile`), and a bare value key
611
712
  // would collide.
612
- /* @__PURE__ */ jsxs(Fragment, { children: [
713
+ /* @__PURE__ */ jsxs(Fragment2, { children: [
613
714
  o.group && o.group !== matches[i - 1]?.group && /* @__PURE__ */ jsx(
614
715
  "li",
615
716
  {
@@ -625,7 +726,7 @@ function InlineEntityCombobox({
625
726
  role: "option",
626
727
  "aria-selected": o.value === value,
627
728
  "aria-disabled": o.disabled || void 0,
628
- onClick: () => commit(o),
729
+ onClick: () => take(o),
629
730
  className: cn(
630
731
  SHEET_ROW_CLASS,
631
732
  o.value === value && "font-medium",
@@ -636,58 +737,100 @@ function InlineEntityCombobox({
636
737
  ) })
637
738
  ] }, `${o.group ?? ""}|${String(o.value)}`)
638
739
  )),
639
- matches.length === 0 && /* @__PURE__ */ jsx("li", { className: "px-4 py-3 text-sm text-[var(--text-muted)]", children: labels.noResults })
740
+ showCreate && /* @__PURE__ */ jsx("li", { role: "presentation", children: /* @__PURE__ */ jsx(
741
+ "button",
742
+ {
743
+ type: "button",
744
+ role: "option",
745
+ "aria-selected": false,
746
+ "aria-disabled": createLocked || void 0,
747
+ onClick: create,
748
+ className: cn(
749
+ SHEET_ROW_CLASS,
750
+ CREATE_ROW_CLASS,
751
+ createLocked && DISABLED_ROW_CLASS
752
+ ),
753
+ children: createRowContent
754
+ }
755
+ ) }),
756
+ matches.length === 0 && !showCreate && /* @__PURE__ */ jsx("li", { className: "px-4 py-3 text-sm text-[var(--text-muted)]", children: labels.noResults })
640
757
  ] })
641
758
  }
642
759
  ),
643
- !isPhone && open && !disabled && matches.length > 0 && /* @__PURE__ */ jsx(SuggestionList, { id: listboxId, anchorRef: fieldRef, panelRef, children: matches.map((o, i) => (
644
- // A group heading is emitted at each group boundary rather than repeating
645
- // the group on every row (feedback #136). `matches` is group-contiguous,
646
- // so comparing with the previous row is enough. Fragment key sits here;
647
- // the heading and the option carry their own list semantics — keyed by
648
- // group AND value, because an option may deliberately appear twice (see
649
- // `reconcile`).
650
- /* @__PURE__ */ jsxs(Fragment, { children: [
651
- o.group && o.group !== matches[i - 1]?.group && /* @__PURE__ */ jsx(
652
- "li",
653
- {
654
- role: "presentation",
655
- className: "px-3 pb-0.5 pt-2 text-[11px] font-semibold uppercase tracking-wide text-[var(--text-muted)] first:pt-1",
656
- children: o.group
657
- }
658
- ),
659
- /* @__PURE__ */ jsx("li", { role: "presentation", children: /* @__PURE__ */ jsxs(
660
- "button",
661
- {
662
- type: "button",
663
- id: optionId(i),
664
- role: "option",
665
- "aria-selected": o.value === value,
666
- "aria-disabled": o.disabled || void 0,
667
- tabIndex: -1,
668
- onMouseDown: (e) => {
669
- e.preventDefault();
670
- commit(o);
671
- },
672
- onMouseEnter: () => {
673
- if (!o.disabled) setActive(i);
674
- },
675
- className: cn(
676
- rowClass(i === active && !o.disabled),
677
- o.disabled && DISABLED_ROW_CLASS,
678
- // Indented under its heading, so the hierarchy is readable at a
679
- // glance instead of inferred from grey trailing text.
680
- o.group && "ps-6",
681
- o.value === value && "font-medium"
682
- ),
683
- children: [
684
- o.label,
685
- o.sublabel && /* @__PURE__ */ jsx("span", { className: "ms-2 text-xs text-[var(--text-placeholder)]", children: o.sublabel })
686
- ]
687
- }
688
- ) })
689
- ] }, `${o.group ?? ""}|${String(o.value)}`)
690
- )) })
760
+ !isPhone && open && !inert && (matches.length > 0 || showCreate) && /* @__PURE__ */ jsxs(SuggestionList, { id: listboxId, anchorRef: fieldRef, panelRef, children: [
761
+ matches.map((o, i) => (
762
+ // A group heading is emitted at each group boundary rather than repeating
763
+ // the group on every row (feedback #136). `matches` is group-contiguous,
764
+ // so comparing with the previous row is enough. Fragment key sits here;
765
+ // the heading and the option carry their own list semantics — keyed by
766
+ // group AND value, because an option may deliberately appear twice (see
767
+ // `reconcile`).
768
+ /* @__PURE__ */ jsxs(Fragment2, { children: [
769
+ o.group && o.group !== matches[i - 1]?.group && /* @__PURE__ */ jsx(
770
+ "li",
771
+ {
772
+ role: "presentation",
773
+ className: "px-3 pb-0.5 pt-2 text-[11px] font-semibold uppercase tracking-wide text-[var(--text-muted)] first:pt-1",
774
+ children: o.group
775
+ }
776
+ ),
777
+ /* @__PURE__ */ jsx("li", { role: "presentation", children: /* @__PURE__ */ jsxs(
778
+ "button",
779
+ {
780
+ type: "button",
781
+ id: optionId(i),
782
+ role: "option",
783
+ "aria-selected": o.value === value,
784
+ "aria-disabled": o.disabled || void 0,
785
+ tabIndex: -1,
786
+ onMouseDown: (e) => {
787
+ e.preventDefault();
788
+ take(o);
789
+ },
790
+ onMouseEnter: () => {
791
+ if (!o.disabled) setActive(i);
792
+ },
793
+ className: cn(
794
+ rowClass(i === active && !o.disabled),
795
+ o.disabled && DISABLED_ROW_CLASS,
796
+ // Indented under its heading, so the hierarchy is readable at a
797
+ // glance instead of inferred from grey trailing text.
798
+ o.group && "ps-6",
799
+ o.value === value && "font-medium"
800
+ ),
801
+ children: [
802
+ o.label,
803
+ o.sublabel && /* @__PURE__ */ jsx("span", { className: "ms-2 text-xs text-[var(--text-placeholder)]", children: o.sublabel })
804
+ ]
805
+ }
806
+ ) })
807
+ ] }, `${o.group ?? ""}|${String(o.value)}`)
808
+ )),
809
+ showCreate && /* @__PURE__ */ jsx("li", { role: "presentation", children: /* @__PURE__ */ jsx(
810
+ "button",
811
+ {
812
+ type: "button",
813
+ id: optionId(createIndex),
814
+ role: "option",
815
+ "aria-selected": false,
816
+ "aria-disabled": createLocked || void 0,
817
+ tabIndex: -1,
818
+ onMouseDown: (e) => {
819
+ e.preventDefault();
820
+ create();
821
+ },
822
+ onMouseEnter: () => {
823
+ if (!createLocked) setActive(createIndex);
824
+ },
825
+ className: cn(
826
+ rowClass(active === createIndex && !createLocked),
827
+ CREATE_ROW_CLASS,
828
+ createLocked && DISABLED_ROW_CLASS
829
+ ),
830
+ children: createRowContent
831
+ }
832
+ ) })
833
+ ] })
691
834
  ] })
692
835
  );
693
836
  }