@tristan2828/ui-foundation 3.19.0 → 3.20.1

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.
@@ -125,7 +125,9 @@ is a filled-in example while the Widgets demo is still here).
125
125
  array of ids on the wire, a repeated any-of list filter, and names from
126
126
  a **lookup by id** on the referenced entity
127
127
  (`GET /widget-categories?ids=1&ids=3`, `getWidgetCategoriesByIds`,
128
- `useWidgetCategoriesByIdsQuery`). Add that `ids` parameter to the referenced
128
+ `useWidgetCategoriesByIdsQuery`), and `searching` from the search
129
+ query's `isPlaceholderData` so Enter waits for the results that match
130
+ what was typed. Add that `ids` parameter to the referenced
129
131
  entity's list endpoint if it lacks one. Never name a picked id from
130
132
  search results alone: a saved pick the current search doesn't return
131
133
  would show without a name. The table looks up every id on its page in
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- export declare function MultiReference<Id extends string | number>({ options, value, onValueChange, getLabel, onSearchChange, id, placeholder, emptyText, 'aria-label': ariaLabel, 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedBy, readOnly, className, }: {
2
+ export declare function MultiReference<Id extends string | number>({ options, value, onValueChange, getLabel, onSearchChange, id, placeholder, emptyText, 'aria-label': ariaLabel, 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedBy, readOnly, searching, className, }: {
3
3
  /** The records the current search returned, in display order. */
4
4
  options: readonly {
5
5
  id: Id;
@@ -27,5 +27,13 @@ export declare function MultiReference<Id extends string | number>({ options, va
27
27
  'aria-describedby'?: string;
28
28
  /** Shown, not changeable: editing in place, while the save is in flight. */
29
29
  readOnly?: boolean;
30
+ /**
31
+ * The options don't answer what's typed yet: the app's search for it is
32
+ * in flight and the list still shows the last one's results (TanStack
33
+ * Query with `placeholderData: (previous) => previous`:
34
+ * `isPlaceholderData`). Enter does nothing until it answers; a click
35
+ * still picks. The list is `aria-busy` meanwhile.
36
+ */
37
+ searching?: boolean;
30
38
  className?: string;
31
39
  }): React.JSX.Element;
@@ -13,15 +13,24 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
13
  // - Matching happens on the server: `onSearchChange` reports what was typed
14
14
  // and `options` is whatever the app's search query returned, so nothing
15
15
  // is filtered here (filter={null}).
16
+ // - Until that search answers, the list still holds the last one's
17
+ // results, its first still highlighted. `searching` says so: Enter
18
+ // waits rather than picking from the old list (where it could even
19
+ // remove a pick: the highlighted item may be one already picked).
16
20
  import * as React from 'react';
17
21
  import { Combobox, ComboboxChip, ComboboxChips, ComboboxChipsInput, ComboboxContent, ComboboxEmpty, ComboboxItem, ComboboxList, ComboboxValue, useComboboxAnchor, } from '../ui/combobox.js';
18
22
  /** Shown on a chip whose name hasn't loaded yet, instead of a bare id. */
19
23
  const PENDING_LABEL = '…';
20
- export function MultiReference({ options, value, onValueChange, getLabel, onSearchChange, id, placeholder, emptyText = 'No matches.', 'aria-label': ariaLabel, 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedBy, readOnly = false, className, }) {
24
+ export function MultiReference({ options, value, onValueChange, getLabel, onSearchChange, id, placeholder, emptyText = 'No matches.', 'aria-label': ariaLabel, 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedBy, readOnly = false, searching = false, className, }) {
21
25
  const anchor = useComboboxAnchor();
22
26
  const label = (option) => getLabel(option) ?? PENDING_LABEL;
23
27
  return (_jsxs(Combobox, { multiple: true, autoHighlight: true,
24
28
  // Ids, not {id,label} objects: Base UI compares items to the value
25
29
  // with Object.is, so the items must have the value's own shape.
26
- items: options.map((option) => option.id), value: value, onValueChange: (next) => onValueChange(next), onInputValueChange: onSearchChange, itemToStringLabel: label, filter: null, readOnly: readOnly, children: [_jsx(ComboboxChips, { ref: anchor, className: className, children: _jsx(ComboboxValue, { children: (selected) => (_jsxs(React.Fragment, { children: [selected.map((option) => (_jsx(ComboboxChip, { children: label(option) }, option))), _jsx(ComboboxChipsInput, { id: id, "aria-label": ariaLabel, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedBy, placeholder: selected.length === 0 ? placeholder : undefined })] })) }) }), _jsxs(ComboboxContent, { anchor: anchor, children: [_jsx(ComboboxEmpty, { children: emptyText }), _jsx(ComboboxList, { children: (option) => (_jsx(ComboboxItem, { value: option, children: label(option) }, option)) })] })] }));
30
+ items: options.map((option) => option.id), value: value, onValueChange: (next) => onValueChange(next), onInputValueChange: onSearchChange, itemToStringLabel: label, filter: null, readOnly: readOnly, children: [_jsx(ComboboxChips, { ref: anchor, className: className, children: _jsx(ComboboxValue, { children: (selected) => (_jsxs(React.Fragment, { children: [selected.map((option) => (_jsx(ComboboxChip, { children: label(option) }, option))), _jsx(ComboboxChipsInput, { id: id, onKeyDown: (event) => {
31
+ if (searching && event.key === 'Enter') {
32
+ event.preventDefault();
33
+ event.preventBaseUIHandler();
34
+ }
35
+ }, "aria-label": ariaLabel, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedBy, placeholder: selected.length === 0 ? placeholder : undefined })] })) }) }), _jsxs(ComboboxContent, { anchor: anchor, children: [_jsx(ComboboxEmpty, { children: emptyText }), _jsx(ComboboxList, { "aria-busy": searching || undefined, children: (option) => (_jsx(ComboboxItem, { value: option, children: label(option) }, option)) })] })] }));
27
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tristan2828/ui-foundation",
3
- "version": "3.19.0",
3
+ "version": "3.20.1",
4
4
  "description": "Shared UI foundation for contract-first, database-backed React apps: shadcn primitives, DataTable/EntityForm composites, auth, the gateway error seam, design tokens, lint rules and the AI playbook.",
5
5
  "license": "MIT",
6
6
  "repository": {