diff --git a/docs/data/base/components/select/SelectIntroduction/system/index.js b/docs/data/base/components/select/SelectIntroduction/system/index.js index 2b3ba86e61..d95d979e5d 100644 --- a/docs/data/base/components/select/SelectIntroduction/system/index.js +++ b/docs/data/base/components/select/SelectIntroduction/system/index.js @@ -5,13 +5,13 @@ import Check from '@mui/icons-material/Check'; export default function UnstyledSelectIntroduction() { return ( - + Trigger {[...Array(100)].map((_, index) => ( - + Item {index + 1} } /> diff --git a/docs/data/base/components/select/SelectIntroduction/system/index.tsx.preview b/docs/data/base/components/select/SelectIntroduction/system/index.tsx.preview index a94386c440..5e0ffa0e2a 100644 --- a/docs/data/base/components/select/SelectIntroduction/system/index.tsx.preview +++ b/docs/data/base/components/select/SelectIntroduction/system/index.tsx.preview @@ -1,10 +1,10 @@ - + Trigger {[...Array(100)].map((_, index) => ( - + Item {index + 1} } /> diff --git a/docs/pages/base-ui/api/select-item.json b/docs/pages/base-ui/api/select-item.json index 573cdfd1ca..238bc0debf 100644 --- a/docs/pages/base-ui/api/select-item.json +++ b/docs/pages/base-ui/api/select-item.json @@ -1,5 +1,6 @@ { "props": { + "value": { "type": { "name": "string" }, "required": true }, "closeOnClick": { "type": { "name": "bool" }, "default": "true" }, "disabled": { "type": { "name": "bool" }, "default": "false" }, "id": { "type": { "name": "string" } }, diff --git a/docs/pages/base-ui/api/select-root.json b/docs/pages/base-ui/api/select-root.json index f82c43edad..9d2b1bfe19 100644 --- a/docs/pages/base-ui/api/select-root.json +++ b/docs/pages/base-ui/api/select-root.json @@ -6,10 +6,16 @@ }, "animated": { "type": { "name": "bool" }, "default": "true" }, "defaultOpen": { "type": { "name": "bool" }, "default": "false" }, + "defaultValue": { "type": { "name": "string" } }, "disabled": { "type": { "name": "bool" }, "default": "false" }, + "id": { "type": { "name": "string" } }, "loop": { "type": { "name": "bool" }, "default": "true" }, + "name": { "type": { "name": "string" } }, "onOpenChange": { "type": { "name": "func" } }, - "open": { "type": { "name": "bool" } } + "open": { "type": { "name": "bool" } }, + "readOnly": { "type": { "name": "bool" }, "default": "false" }, + "required": { "type": { "name": "bool" }, "default": "false" }, + "value": { "type": { "name": "string" } } }, "name": "SelectRoot", "imports": ["import * as Select from '@base_ui/react/Select';\nconst SelectRoot = Select.Root;"], diff --git a/docs/translations/api-docs/select-item/select-item.json b/docs/translations/api-docs/select-item/select-item.json index 46541ccb2f..685e50293c 100644 --- a/docs/translations/api-docs/select-item/select-item.json +++ b/docs/translations/api-docs/select-item/select-item.json @@ -1,15 +1,16 @@ { - "componentDescription": "An unstyled menu item to be used within a Menu.", + "componentDescription": "An unstyled select item to be used within a Select.", "propDescriptions": { "closeOnClick": { - "description": "If true, the menu will close when the menu item is clicked." + "description": "If true, the select will close when the select item is clicked." }, - "disabled": { "description": "If true, the menu item will be disabled." }, - "id": { "description": "The id of the menu item." }, + "disabled": { "description": "If true, the select item will be disabled." }, + "id": { "description": "The id of the select item." }, "label": { - "description": "A text representation of the menu item's content. Used for keyboard text navigation matching." + "description": "A text representation of the select item's content. Used for keyboard text navigation matching." }, - "onClick": { "description": "The click handler for the menu item." } + "onClick": { "description": "The click handler for the select item." }, + "value": { "description": "The value of the select item." } }, "classDescriptions": {} } diff --git a/docs/translations/api-docs/select-root/select-root.json b/docs/translations/api-docs/select-root/select-root.json index 469424d40a..5565e95b76 100644 --- a/docs/translations/api-docs/select-root/select-root.json +++ b/docs/translations/api-docs/select-root/select-root.json @@ -5,19 +5,25 @@ "description": "Determines the type of alignment mode. selected-item aligns the popup so that the selected item appears over the trigger, while trigger aligns the popup using standard anchor positioning." }, "animated": { - "description": "If true, the Menu supports CSS-based animations and transitions. It is kept in the DOM until the animation completes." + "description": "If true, the Select supports CSS-based animations and transitions. It is kept in the DOM until the animation completes." }, - "defaultOpen": { "description": "If true, the Menu is initially open." }, - "disabled": { "description": "If true, the Menu is disabled." }, + "defaultOpen": { "description": "If true, the Select is initially open." }, + "defaultValue": { "description": "The default value of the select." }, + "disabled": { "description": "If true, the Select is disabled." }, + "id": { "description": "The id of the Select." }, "loop": { "description": "If true, using keyboard navigation will wrap focus to the other end of the list once the end is reached." }, + "name": { "description": "The name of the Select in the owning form." }, "onOpenChange": { "description": "Callback fired when the component requests to be opened or closed." }, "open": { "description": "Allows to control whether the dropdown is open. This is a controlled counterpart of defaultOpen." - } + }, + "readOnly": { "description": "If true, the Select is read-only." }, + "required": { "description": "If true, the Select is required." }, + "value": { "description": "The value of the select." } }, "classDescriptions": {} } diff --git a/packages/mui-base/src/Select/Item/SelectItem.tsx b/packages/mui-base/src/Select/Item/SelectItem.tsx index c394251f8f..0d38cfffd7 100644 --- a/packages/mui-base/src/Select/Item/SelectItem.tsx +++ b/packages/mui-base/src/Select/Item/SelectItem.tsx @@ -243,6 +243,10 @@ SelectItem.propTypes /* remove-proptypes */ = { * The click handler for the select item. */ onClick: PropTypes.func, + /** + * The value of the select item. + */ + value: PropTypes.string.isRequired, } as any; export { SelectItem }; diff --git a/packages/mui-base/src/Select/Positioner/SelectPositioner.tsx b/packages/mui-base/src/Select/Positioner/SelectPositioner.tsx index c6ead376b7..ea988b76f7 100644 --- a/packages/mui-base/src/Select/Positioner/SelectPositioner.tsx +++ b/packages/mui-base/src/Select/Positioner/SelectPositioner.tsx @@ -17,13 +17,16 @@ import { useForkRef } from '../../utils/useForkRef'; import { useSelectPositioner } from './useSelectPositioner'; import { HTMLElementType } from '../../utils/proptypes'; import { visuallyHidden } from '../../utils/visuallyHidden'; +import { useFieldRootContext } from '../../Field/Root/FieldRootContext'; +import { useEnhancedEffect } from '../../utils/useEnhancedEffect'; +import { useId } from '../../utils/useId'; function findSelectItems(root: React.ReactElement) { const selectItems: React.ReactElement[] = []; React.Children.forEach(root.props?.children, (child) => { if (React.isValidElement(child)) { const childProps = child.props as any; - if (childProps?.value) { + if (childProps?.value != null) { selectItems.push(child); } else if (childProps?.children) { selectItems.push(...findSelectItems(child)); @@ -82,8 +85,23 @@ const SelectPositioner = React.forwardRef(function SelectPositioner( innerFallback, setInnerFallback, selectedIndex, + name, + required, + disabled, + id: idProp, } = useSelectRootContext(); + const { setControlId } = useFieldRootContext(); + + const id = useId(idProp); + + useEnhancedEffect(() => { + setControlId(id); + return () => { + setControlId(undefined); + }; + }, [id, setControlId]); + const positioner = useSelectPositioner({ anchor: anchor || triggerElement, floatingRootContext, @@ -168,14 +186,16 @@ const SelectPositioner = React.forwardRef(function SelectPositioner( const positionerElement = renderElement(); const selectItems = findSelectItems(positionerElement); - const mountedItemsElement = ; + const mountedItemsElement = keepMounted ? null : ; const nativeSelectElement = (