Skip to content

Commit c32adfc

Browse files
committed
Update items traversal
1 parent cd8f086 commit c32adfc

File tree

14 files changed

+282
-92
lines changed

14 files changed

+282
-92
lines changed

docs/data/base/components/select/SelectIntroduction/system/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ import Check from '@mui/icons-material/Check';
55

66
export default function UnstyledSelectIntroduction() {
77
return (
8-
<Select.Root>
8+
<Select.Root value="item-2">
99
<SelectTrigger>Trigger</SelectTrigger>
1010
<Select.Backdrop />
1111
<Select.Positioner alignment="start" alignmentOffset={-4}>
1212
<SelectPopup>
1313
{[...Array(100)].map((_, index) => (
14-
<SelectItem key={index}>
14+
<SelectItem key={index} value={`item-${index + 1}`}>
1515
Item {index + 1}
1616
<SelectItemIndicator render={<Check fontSize="small" aria-hidden />} />
1717
</SelectItem>

docs/data/base/components/select/SelectIntroduction/system/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ import Check from '@mui/icons-material/Check';
55

66
export default function UnstyledSelectIntroduction() {
77
return (
8-
<Select.Root>
8+
<Select.Root value="item-2">
99
<SelectTrigger>Trigger</SelectTrigger>
1010
<Select.Backdrop />
1111
<Select.Positioner alignment="start" alignmentOffset={-4}>
1212
<SelectPopup>
1313
{[...Array(100)].map((_, index) => (
14-
<SelectItem key={index}>
14+
<SelectItem key={index} value={`item-${index + 1}`}>
1515
Item {index + 1}
1616
<SelectItemIndicator render={<Check fontSize="small" aria-hidden />} />
1717
</SelectItem>

docs/data/base/components/select/SelectIntroduction/system/index.tsx.preview

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
<Select.Root>
1+
<Select.Root value="item-2">
22
<SelectTrigger>Trigger</SelectTrigger>
33
<Select.Backdrop />
44
<Select.Positioner alignment="start" alignmentOffset={-4}>
55
<SelectPopup>
66
{[...Array(100)].map((_, index) => (
7-
<SelectItem key={index}>
7+
<SelectItem key={index} value={`item-${index + 1}`}>
88
Item {index + 1}
99
<SelectItemIndicator render={<Check fontSize="small" aria-hidden />} />
1010
</SelectItem>

docs/pages/base-ui/api/select-item.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"props": {
3+
"value": { "type": { "name": "string" }, "required": true },
34
"closeOnClick": { "type": { "name": "bool" }, "default": "true" },
45
"disabled": { "type": { "name": "bool" }, "default": "false" },
56
"id": { "type": { "name": "string" } },

docs/pages/base-ui/api/select-root.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,16 @@
66
},
77
"animated": { "type": { "name": "bool" }, "default": "true" },
88
"defaultOpen": { "type": { "name": "bool" }, "default": "false" },
9+
"defaultValue": { "type": { "name": "string" } },
910
"disabled": { "type": { "name": "bool" }, "default": "false" },
11+
"id": { "type": { "name": "string" } },
1012
"loop": { "type": { "name": "bool" }, "default": "true" },
13+
"name": { "type": { "name": "string" } },
1114
"onOpenChange": { "type": { "name": "func" } },
12-
"open": { "type": { "name": "bool" } }
15+
"open": { "type": { "name": "bool" } },
16+
"readOnly": { "type": { "name": "bool" }, "default": "false" },
17+
"required": { "type": { "name": "bool" }, "default": "false" },
18+
"value": { "type": { "name": "string" } }
1319
},
1420
"name": "SelectRoot",
1521
"imports": ["import * as Select from '@base_ui/react/Select';\nconst SelectRoot = Select.Root;"],
Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
{
2-
"componentDescription": "An unstyled menu item to be used within a Menu.",
2+
"componentDescription": "An unstyled select item to be used within a Select.",
33
"propDescriptions": {
44
"closeOnClick": {
5-
"description": "If <code>true</code>, the menu will close when the menu item is clicked."
5+
"description": "If <code>true</code>, the select will close when the select item is clicked."
66
},
7-
"disabled": { "description": "If <code>true</code>, the menu item will be disabled." },
8-
"id": { "description": "The id of the menu item." },
7+
"disabled": { "description": "If <code>true</code>, the select item will be disabled." },
8+
"id": { "description": "The id of the select item." },
99
"label": {
10-
"description": "A text representation of the menu item&#39;s content. Used for keyboard text navigation matching."
10+
"description": "A text representation of the select item&#39;s content. Used for keyboard text navigation matching."
1111
},
12-
"onClick": { "description": "The click handler for the menu item." }
12+
"onClick": { "description": "The click handler for the select item." },
13+
"value": { "description": "The value of the select item." }
1314
},
1415
"classDescriptions": {}
1516
}

docs/translations/api-docs/select-root/select-root.json

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,25 @@
55
"description": "Determines the type of alignment mode. <code>selected-item</code> aligns the popup so that the selected item appears over the trigger, while <code>trigger</code> aligns the popup using standard anchor positioning."
66
},
77
"animated": {
8-
"description": "If <code>true</code>, the Menu supports CSS-based animations and transitions. It is kept in the DOM until the animation completes."
8+
"description": "If <code>true</code>, the Select supports CSS-based animations and transitions. It is kept in the DOM until the animation completes."
99
},
10-
"defaultOpen": { "description": "If <code>true</code>, the Menu is initially open." },
11-
"disabled": { "description": "If <code>true</code>, the Menu is disabled." },
10+
"defaultOpen": { "description": "If <code>true</code>, the Select is initially open." },
11+
"defaultValue": { "description": "The default value of the select." },
12+
"disabled": { "description": "If <code>true</code>, the Select is disabled." },
13+
"id": { "description": "The id of the Select." },
1214
"loop": {
1315
"description": "If <code>true</code>, using keyboard navigation will wrap focus to the other end of the list once the end is reached."
1416
},
17+
"name": { "description": "The name of the Select in the owning form." },
1518
"onOpenChange": {
1619
"description": "Callback fired when the component requests to be opened or closed."
1720
},
1821
"open": {
1922
"description": "Allows to control whether the dropdown is open. This is a controlled counterpart of <code>defaultOpen</code>."
20-
}
23+
},
24+
"readOnly": { "description": "If <code>true</code>, the Select is read-only." },
25+
"required": { "description": "If <code>true</code>, the Select is required." },
26+
"value": { "description": "The value of the select." }
2127
},
2228
"classDescriptions": {}
2329
}

packages/mui-base/src/Select/Item/SelectItem.tsx

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { useForkRef } from '../../utils/useForkRef';
1111
import { useEventCallback } from '../../utils/useEventCallback';
1212
import { SelectItemContext } from './SelectItemContext';
1313
import { commonStyleHooks } from '../utils/commonStyleHooks';
14+
import { useEnhancedEffect } from '../../utils/useEnhancedEffect';
1415

1516
const InnerSelectItem = React.memo(
1617
React.forwardRef(function InnerSelectItem(
@@ -74,44 +75,59 @@ const InnerSelectItem = React.memo(
7475
);
7576

7677
/**
77-
* An unstyled menu item to be used within a Menu.
78+
* An unstyled select item to be used within a Select.
7879
*
7980
* Demos:
8081
*
81-
* - [Menu](https://mui.com/base-ui/react-menu/)
82+
* - [Select](https://mui.com/base-ui/react-select/)
8283
*
8384
* API:
8485
*
85-
* - [SelectItem API](https://mui.com/base-ui/react-menu/components-api/#menu-item)
86+
* - [SelectItem API](https://mui.com/base-ui/react-select/components-api/#select-item)
8687
*/
8788
const SelectItem = React.forwardRef(function SelectItem(
8889
props: SelectItem.Props,
8990
forwardedRef: React.ForwardedRef<Element>,
9091
) {
91-
const { id: idProp, label, ...otherProps } = props;
92+
const { id: idProp, value: valueProp, label, ...otherProps } = props;
9293

9394
const {
95+
setValue,
9496
open,
9597
getItemProps,
9698
activeIndex,
9799
selectedIndex,
98100
setOpen,
99101
typingRef,
100-
setSelectedIndex,
101102
selectionRef,
103+
valuesRef,
102104
} = useSelectRootContext();
103105

104106
const [item, setItem] = React.useState<Element | null>(null);
105-
const listItem = useListItem({ label: label ?? item?.textContent });
107+
const itemLabel = label ?? item?.textContent ?? null;
108+
const listItem = useListItem({ label: itemLabel });
106109
const mergedRef = useForkRef(forwardedRef, listItem.ref, setItem);
107110

111+
useEnhancedEffect(() => {
112+
if (listItem.index === -1) {
113+
return undefined;
114+
}
115+
116+
const values = valuesRef.current;
117+
values[listItem.index] = valueProp;
118+
119+
return () => {
120+
values[listItem.index] = null;
121+
};
122+
}, [listItem.index, valueProp, valuesRef]);
123+
108124
const id = useId(idProp);
109125

110126
const highlighted = listItem.index === activeIndex;
111127
const selected = listItem.index === selectedIndex;
112128

113129
const handleSelect = useEventCallback(() => {
114-
setSelectedIndex(listItem.index);
130+
setValue(valueProp);
115131
});
116132

117133
const contextValue = React.useMemo(() => ({ open, selected }), [open, selected]);
@@ -139,7 +155,7 @@ const SelectItem = React.forwardRef(function SelectItem(
139155
);
140156
});
141157

142-
interface InnerSelectItemProps extends SelectItem.Props {
158+
interface InnerSelectItemProps extends Omit<SelectItem.Props, 'value'> {
143159
highlighted: boolean;
144160
selected: boolean;
145161
getItemProps: UseInteractionsReturn['getItemProps'];
@@ -164,25 +180,29 @@ namespace SelectItem {
164180
export interface Props extends BaseUIComponentProps<'div', OwnerState> {
165181
children?: React.ReactNode;
166182
/**
167-
* The click handler for the menu item.
183+
* The value of the select item.
184+
*/
185+
value: string;
186+
/**
187+
* The click handler for the select item.
168188
*/
169189
onClick?: React.MouseEventHandler<HTMLElement>;
170190
/**
171-
* If `true`, the menu item will be disabled.
191+
* If `true`, the select item will be disabled.
172192
* @default false
173193
*/
174194
disabled?: boolean;
175195
/**
176-
* A text representation of the menu item's content.
196+
* A text representation of the select item's content.
177197
* Used for keyboard text navigation matching.
178198
*/
179199
label?: string;
180200
/**
181-
* The id of the menu item.
201+
* The id of the select item.
182202
*/
183203
id?: string;
184204
/**
185-
* If `true`, the menu will close when the menu item is clicked.
205+
* If `true`, the select will close when the select item is clicked.
186206
*
187207
* @default true
188208
*/
@@ -200,29 +220,33 @@ SelectItem.propTypes /* remove-proptypes */ = {
200220
*/
201221
children: PropTypes.node,
202222
/**
203-
* If `true`, the menu will close when the menu item is clicked.
223+
* If `true`, the select will close when the select item is clicked.
204224
*
205225
* @default true
206226
*/
207227
closeOnClick: PropTypes.bool,
208228
/**
209-
* If `true`, the menu item will be disabled.
229+
* If `true`, the select item will be disabled.
210230
* @default false
211231
*/
212232
disabled: PropTypes.bool,
213233
/**
214-
* The id of the menu item.
234+
* The id of the select item.
215235
*/
216236
id: PropTypes.string,
217237
/**
218-
* A text representation of the menu item's content.
238+
* A text representation of the select item's content.
219239
* Used for keyboard text navigation matching.
220240
*/
221241
label: PropTypes.string,
222242
/**
223-
* The click handler for the menu item.
243+
* The click handler for the select item.
224244
*/
225245
onClick: PropTypes.func,
246+
/**
247+
* The value of the select item.
248+
*/
249+
value: PropTypes.string.isRequired,
226250
} as any;
227251

228252
export { SelectItem };

packages/mui-base/src/Select/Item/useSelectItem.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ export function useSelectItem(params: useSelectItem.Parameters): useSelectItem.R
1616
const {
1717
disabled = false,
1818
highlighted,
19-
selected,
2019
id,
2120
ref: externalRef,
2221
setOpen,
@@ -69,11 +68,7 @@ export function useSelectItem(params: useSelectItem.Parameters): useSelectItem.R
6968
return;
7069
}
7170

72-
if (selected) {
73-
if (selectionRef.current.select) {
74-
commitSelection(event.nativeEvent);
75-
}
76-
} else {
71+
if (selectionRef.current.select) {
7772
commitSelection(event.nativeEvent);
7873
}
7974

@@ -82,7 +77,7 @@ export function useSelectItem(params: useSelectItem.Parameters): useSelectItem.R
8277
}),
8378
);
8479
},
85-
[commitSelection, getButtonProps, highlighted, id, selected, selectionRef, typingRef],
80+
[commitSelection, getButtonProps, highlighted, id, selectionRef, typingRef],
8681
);
8782

8883
return React.useMemo(

0 commit comments

Comments
 (0)