UNPKG

@base-ui-components/react

Version:

Base UI is a library of headless ('unstyled') React components and low-level hooks. You gain complete control over your app's CSS and accessibility features.

104 lines (100 loc) 4.08 kB
'use client'; import * as React from 'react'; import { useStore } from '@base-ui-components/utils/store'; import { useRenderElement } from "../../utils/useRenderElement.js"; import { useComboboxRootContext } from "../root/ComboboxRootContext.js"; import { useComboboxChipContext } from "../chip/ComboboxChipContext.js"; import { useButton } from "../../use-button/index.js"; import { stopEvent } from "../../floating-ui-react/utils.js"; import { selectors } from "../store.js"; import { createChangeEventDetails } from "../../utils/createBaseUIEventDetails.js"; import { REASONS } from "../../utils/reasons.js"; import { findItemIndex } from "../../utils/itemEquality.js"; /** * A button to remove a chip. * Renders a `<button>` element. */ export const ComboboxChipRemove = /*#__PURE__*/React.forwardRef(function ComboboxChipRemove(componentProps, forwardedRef) { const { render, className, nativeButton = true, ...elementProps } = componentProps; const store = useComboboxRootContext(); const { index } = useComboboxChipContext(); const disabled = useStore(store, selectors.disabled); const readOnly = useStore(store, selectors.readOnly); const selectedValue = useStore(store, selectors.selectedValue); const isItemEqualToValue = useStore(store, selectors.isItemEqualToValue); const { buttonRef, getButtonProps } = useButton({ native: nativeButton, disabled: disabled || readOnly, focusableWhenDisabled: true }); const state = React.useMemo(() => ({ disabled }), [disabled]); const element = useRenderElement('button', componentProps, { ref: [forwardedRef, buttonRef], state, props: [{ tabIndex: -1, disabled, 'aria-readonly': readOnly || undefined, onClick(event) { if (disabled || readOnly) { return; } const eventDetails = createChangeEventDetails(REASONS.chipRemovePress, event.nativeEvent); // If the removed chip was the active item, clear highlight const activeIndex = store.state.activeIndex; const removedItem = selectedValue[index]; // Try current visible list first; if not found, it's filtered out. No need // to clear highlight in that case since it can't equal activeIndex. const removedIndex = findItemIndex(store.state.valuesRef.current, removedItem, isItemEqualToValue); if (removedIndex !== -1 && activeIndex === removedIndex) { store.state.setIndices({ activeIndex: null, type: store.state.keyboardActiveRef.current ? 'pointer' : 'keyboard' }); } store.state.setSelectedValue(selectedValue.filter((_, i) => i !== index), eventDetails); if (!eventDetails.isPropagationAllowed) { event.stopPropagation(); } store.state.inputRef.current?.focus(); }, onKeyDown(event) { if (disabled || readOnly) { return; } const eventDetails = createChangeEventDetails(REASONS.chipRemovePress, event.nativeEvent); if (event.key === 'Enter' || event.key === ' ') { // If the removed chip was the active item, clear highlight const activeIndex = store.state.activeIndex; const removedItem = selectedValue[index]; const removedIndex = findItemIndex(store.state.valuesRef.current, removedItem, isItemEqualToValue); if (removedIndex !== -1 && activeIndex === removedIndex) { store.state.setIndices({ activeIndex: null, type: store.state.keyboardActiveRef.current ? 'pointer' : 'keyboard' }); } store.state.setSelectedValue(selectedValue.filter((_, i) => i !== index), eventDetails); if (!eventDetails.isPropagationAllowed) { stopEvent(event); } store.state.inputRef.current?.focus(); } } }, elementProps, getButtonProps] }); return element; }); if (process.env.NODE_ENV !== "production") ComboboxChipRemove.displayName = "ComboboxChipRemove";