Skip to content

Commit c55d40b

Browse files
authored
Merge branch 'master' into renovate/mui-infra-packages
Signed-off-by: Connor Davis <[email protected]>
2 parents fc7e43f + 39b7fe8 commit c55d40b

File tree

17 files changed

+446
-512
lines changed

17 files changed

+446
-512
lines changed

docs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
"radix-ui": "^1.4.3",
4646
"react": "^19.2.3",
4747
"react-dom": "^19.2.3",
48-
"react-error-boundary": "6.0.3",
48+
"react-error-boundary": "6.1.0",
4949
"react-hook-form": "^7.71.1",
5050
"react-is": "^19.2.3",
5151
"rehype-pretty-code": "^0.14.1",

docs/src/components/Demo/DemoErrorFallback.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { FallbackProps } from 'react-error-boundary';
1+
import { FallbackProps, getErrorMessage } from 'react-error-boundary';
22

33
export function DemoErrorFallback(props: FallbackProps) {
44
const { error, resetErrorBoundary } = props;
55

66
return (
77
<div role="alert">
88
<p>There was an error while rendering the demo.</p>
9-
<pre>{error.message}</pre>
9+
<pre>{getErrorMessage(error) ?? 'Unknown error'}</pre>
1010
<button type="button" onClick={resetErrorBoundary}>
1111
Try again
1212
</button>

docs/src/components/Search/SearchBar.tsx

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -189,10 +189,39 @@ export function SearchBar({
189189
[search],
190190
);
191191

192+
const highlightedResultRef = React.useRef<SearchResult | undefined>(undefined);
193+
192194
const handleItemClick = React.useCallback(() => {
193195
handleCloseDialog(false);
194196
}, [handleCloseDialog]);
195197

198+
const handleItemHighlighted = React.useCallback((item: SearchResult | undefined) => {
199+
highlightedResultRef.current = item;
200+
}, []);
201+
202+
const handleKeyDownCapture = React.useCallback(
203+
(event: React.KeyboardEvent) => {
204+
// Only handle Enter with modifiers
205+
if (event.key !== 'Enter' || (!event.metaKey && !event.ctrlKey && !event.altKey)) {
206+
return;
207+
}
208+
209+
const highlightedResult = highlightedResultRef.current;
210+
if (!highlightedResult) {
211+
return;
212+
}
213+
214+
// Prevent the Input/List handlers from processing this
215+
event.preventDefault();
216+
event.stopPropagation();
217+
218+
// Open in new tab
219+
const url = buildResultUrl(highlightedResult);
220+
window.open(url, '_blank', 'noopener,noreferrer');
221+
},
222+
[buildResultUrl],
223+
);
224+
196225
const showCmdSymbol = React.useSyncExternalStore(
197226
() => () => {},
198227
() => enableKeyboardShortcut && isMac,
@@ -209,10 +238,11 @@ export function SearchBar({
209238
ref={inputRef}
210239
placeholder="Search"
211240
className="w-full border-0 bg-transparent text-base tracking-[0.016em] font-normal text-gray-900 placeholder:text-gray-500 focus:outline-none"
241+
onKeyDownCapture={handleKeyDownCapture}
212242
/>
213243
</div>
214244
),
215-
[],
245+
[handleKeyDownCapture],
216246
);
217247

218248
// Memoized callback for itemToStringValue
@@ -283,6 +313,7 @@ export function SearchBar({
283313
items={searchResults.results}
284314
onValueChange={handleValueChange}
285315
onOpenChange={handleAutocompleteEscape}
316+
onItemHighlighted={handleItemHighlighted}
286317
open
287318
inline
288319
itemToStringValue={itemToStringValue}
@@ -300,7 +331,10 @@ export function SearchBar({
300331
{searchResults.results.length === 0 ? (
301332
<EmptyState />
302333
) : (
303-
<Autocomplete.List className="outline-0 p-2">
334+
<Autocomplete.List
335+
className="outline-0 p-2"
336+
onKeyDownCapture={handleKeyDownCapture}
337+
>
304338
{renderResultsList}
305339
</Autocomplete.List>
306340
)}
@@ -343,6 +377,7 @@ export function SearchBar({
343377
items={searchResults.results}
344378
onValueChange={handleValueChange}
345379
onOpenChange={handleAutocompleteEscape}
380+
onItemHighlighted={handleItemHighlighted}
346381
open
347382
inline
348383
itemToStringValue={itemToStringValue}
@@ -354,7 +389,10 @@ export function SearchBar({
354389
{searchResults.results.length === 0 ? (
355390
<EmptyState />
356391
) : (
357-
<Autocomplete.List className="outline-0 overflow-y-auto p-2 scroll-pt-9 scroll-pb-2 overscroll-contain max-h-[min(22.5rem,var(--available-height))] rounded-b-[5px]">
392+
<Autocomplete.List
393+
className="outline-0 overflow-y-auto p-2 scroll-pt-9 scroll-pb-2 overscroll-contain max-h-[min(22.5rem,var(--available-height))] rounded-b-[5px]"
394+
onKeyDownCapture={handleKeyDownCapture}
395+
>
358396
{renderResultsList}
359397
</Autocomplete.List>
360398
)}

package.json

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
"build": "lerna run --no-private build",
99
"release:version": "lerna version --no-changelog --no-push --no-git-tag-version --no-private",
1010
"release:build": "lerna run --concurrency 8 --no-private build --skip-nx-cache",
11-
"release:changelog": "tsx scripts/releaseChangelog.mts",
11+
"release:changelog": "code-infra generate-changelog",
12+
"release:changelog:docs": "FORMAT=docs code-infra generate-changelog",
1213
"release:publish": "code-infra publish --github-release",
1314
"release:publish:dry-run": "code-infra publish --github-release --dry-run ",
1415
"docs:api": "pnpm --filter api-docs-builder start",
@@ -62,7 +63,7 @@
6263
"@arethetypeswrong/cli": "0.18.2",
6364
"@babel/plugin-transform-react-constant-elements": "7.27.1",
6465
"@base-ui/monorepo-tests": "workspace:*",
65-
"@mui/internal-code-infra": "0.0.3-canary.80",
66+
"@mui/internal-code-infra": "0.0.3-canary.82",
6667
"@mui/internal-netlify-cache": "0.0.2-canary.1",
6768
"@mui/internal-test-utils": "2.0.18-canary.5",
6869
"@next/eslint-plugin-next": "16.1.4",
@@ -77,11 +78,9 @@
7778
"@vitest/coverage-istanbul": "4.0.17",
7879
"@vitest/ui": "4.0.17",
7980
"chai-dom": "1.12.1",
80-
"chalk": "5.6.2",
8181
"concurrently": "9.2.1",
8282
"cross-env": "10.1.0",
8383
"docs": "workspace:^",
84-
"es-toolkit": "1.44.0",
8584
"eslint": "9.39.2",
8685
"execa": "9.6.1",
8786
"globby": "16.1.0",
@@ -102,8 +101,7 @@
102101
"tsx": "4.21.0",
103102
"typescript": "5.9.3",
104103
"vite": "7.3.1",
105-
"vitest": "4.0.17",
106-
"yargs": "18.0.0"
104+
"vitest": "4.0.17"
107105
},
108106
"packageManager": "[email protected]",
109107
"engines": {

packages/react/src/combobox/root/AriaCombobox.tsx

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -855,52 +855,6 @@ export function AriaCombobox<Value = any, Mode extends SelectionMode = 'none'>(
855855
store,
856856
]);
857857

858-
// When the available items change, ensure the selected value(s) remain valid.
859-
// - Single: if current selection is removed, fall back to defaultSelectedValue if it exists in the list; else null.
860-
// - Multiple: drop any removed selections.
861-
useIsoLayoutEffect(() => {
862-
if (!items || selectionMode === 'none') {
863-
return;
864-
}
865-
866-
const registry = flatItems;
867-
868-
if (multiple) {
869-
const current = Array.isArray(selectedValue) ? selectedValue : EMPTY_ARRAY;
870-
const next = current.filter((v) => itemIncludes(registry, v, store.state.isItemEqualToValue));
871-
if (next.length !== current.length) {
872-
setSelectedValue(next, createChangeEventDetails(REASONS.none));
873-
}
874-
return;
875-
}
876-
877-
const isStillPresent =
878-
selectedValue == null ||
879-
itemIncludes(registry, selectedValue, store.state.isItemEqualToValue);
880-
if (isStillPresent) {
881-
return;
882-
}
883-
884-
let fallback = null;
885-
if (
886-
defaultSelectedValue != null &&
887-
itemIncludes(registry, defaultSelectedValue, store.state.isItemEqualToValue)
888-
) {
889-
fallback = defaultSelectedValue;
890-
}
891-
892-
setSelectedValue(fallback, createChangeEventDetails(REASONS.none));
893-
}, [
894-
items,
895-
flatItems,
896-
multiple,
897-
selectionMode,
898-
selectedValue,
899-
defaultSelectedValue,
900-
store,
901-
setSelectedValue,
902-
]);
903-
904858
useIsoLayoutEffect(() => {
905859
if (selectionMode === 'none') {
906860
setFilled(String(inputValue) !== '');

packages/react/src/combobox/root/ComboboxRoot.test.tsx

Lines changed: 19 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -297,60 +297,6 @@ describe('<Combobox.Root />', () => {
297297
expect(cherryOption).to.have.attribute('data-selected', '');
298298
});
299299

300-
it('reconciles a controlled value when the selected item is removed', async () => {
301-
const handleValueChange = spy();
302-
303-
function App() {
304-
const [items, setItems] = React.useState(['a', 'b']);
305-
const [value, setValue] = React.useState<string | null>('b');
306-
307-
return (
308-
<React.Fragment>
309-
<button type="button" onClick={() => setItems(['a'])}>
310-
Remove
311-
</button>
312-
<Combobox.Root
313-
items={items}
314-
value={value}
315-
defaultValue="a"
316-
onValueChange={(nextValue, details) => {
317-
handleValueChange(nextValue, details);
318-
setValue(nextValue);
319-
}}
320-
>
321-
<Combobox.Input data-testid="input" />
322-
<Combobox.Portal>
323-
<Combobox.Positioner>
324-
<Combobox.Popup>
325-
<Combobox.List>
326-
{(item: string) => (
327-
<Combobox.Item key={item} value={item}>
328-
{item}
329-
</Combobox.Item>
330-
)}
331-
</Combobox.List>
332-
</Combobox.Popup>
333-
</Combobox.Positioner>
334-
</Combobox.Portal>
335-
</Combobox.Root>
336-
</React.Fragment>
337-
);
338-
}
339-
340-
const { user } = await render(<App />);
341-
342-
expect(screen.getByTestId('input')).to.have.value('b');
343-
344-
await user.click(screen.getByRole('button', { name: 'Remove' }));
345-
346-
await waitFor(() => {
347-
expect(handleValueChange.callCount).to.equal(1);
348-
expect(handleValueChange.firstCall.args[0]).to.equal('a');
349-
expect(handleValueChange.firstCall.args[1].reason).to.equal(REASONS.none);
350-
expect(screen.getByTestId('input')).to.have.value('a');
351-
});
352-
});
353-
354300
it('should not auto-close popup when open state is controlled', async () => {
355301
const items = ['apple', 'banana', 'cherry'];
356302

@@ -523,57 +469,6 @@ describe('<Combobox.Root />', () => {
523469
});
524470

525471
describe('multiple', () => {
526-
it('reconciles a controlled value when selected items are removed', async () => {
527-
const handleValueChange = spy();
528-
529-
function App() {
530-
const [items, setItems] = React.useState(['a', 'b', 'c']);
531-
const [value, setValue] = React.useState(['a', 'b']);
532-
533-
return (
534-
<React.Fragment>
535-
<button type="button" onClick={() => setItems(['a', 'c'])}>
536-
Remove
537-
</button>
538-
<Combobox.Root
539-
items={items}
540-
multiple
541-
value={value}
542-
onValueChange={(nextValue, details) => {
543-
handleValueChange(nextValue, details);
544-
setValue(nextValue);
545-
}}
546-
>
547-
<Combobox.Input />
548-
<Combobox.Portal>
549-
<Combobox.Positioner>
550-
<Combobox.Popup>
551-
<Combobox.List>
552-
{(item: string) => (
553-
<Combobox.Item key={item} value={item}>
554-
{item}
555-
</Combobox.Item>
556-
)}
557-
</Combobox.List>
558-
</Combobox.Popup>
559-
</Combobox.Positioner>
560-
</Combobox.Portal>
561-
</Combobox.Root>
562-
</React.Fragment>
563-
);
564-
}
565-
566-
const { user } = await render(<App />);
567-
568-
await user.click(screen.getByRole('button', { name: 'Remove' }));
569-
570-
await waitFor(() => {
571-
expect(handleValueChange.callCount).to.equal(1);
572-
expect(handleValueChange.firstCall.args[0]).to.deep.equal(['a']);
573-
expect(handleValueChange.firstCall.args[1].reason).to.equal(REASONS.none);
574-
});
575-
});
576-
577472
it('should handle multiple selection', async () => {
578473
const handleValueChange = spy();
579474

@@ -1606,6 +1501,24 @@ describe('<Combobox.Root />', () => {
16061501
await user.click(screen.getByText('Canada'));
16071502
expect(input).to.have.value('Canada');
16081503
});
1504+
1505+
it('shows the label for a controlled object value not in items', async () => {
1506+
const value = { country: 'Japan', code: 'JP' };
1507+
1508+
await render(
1509+
<Combobox.Root
1510+
items={items}
1511+
value={value}
1512+
itemToStringLabel={(item) => item.country}
1513+
itemToStringValue={(item) => item.code}
1514+
>
1515+
<Combobox.Input />
1516+
</Combobox.Root>,
1517+
);
1518+
1519+
const input = screen.getByRole('combobox');
1520+
expect(input).to.have.value('Japan');
1521+
});
16091522
});
16101523

16111524
describe('prop: itemToStringValue', () => {
@@ -1833,7 +1746,7 @@ describe('<Combobox.Root />', () => {
18331746
);
18341747

18351748
const input = screen.getByRole<HTMLInputElement>('combobox');
1836-
expect(input).to.have.value('');
1749+
expect(input).to.have.value('banana');
18371750

18381751
await setProps({ items: ['apple', 'banana', 'bread'] });
18391752

@@ -1842,10 +1755,6 @@ describe('<Combobox.Root />', () => {
18421755
await setProps({ items: ['banana'] });
18431756

18441757
expect(input).to.have.value('banana');
1845-
1846-
await setProps({ items: ['grape', 'apple'] });
1847-
1848-
expect(input).to.have.value('');
18491758
});
18501759
});
18511760

0 commit comments

Comments
 (0)