@crab-dev/rc-select 0.0.5 → 0.1.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.
@@ -1,3 +1,3 @@
1
- export declare const vars: { readonly 'option.checkbox.width': string; readonly 'option.background-color-hover': string; readonly 'option.color-selected': string; readonly 'option.background-color-selected': string; readonly 'option.color-disabled': string; readonly 'option.highlight.background-color': string; readonly 'option.background-color-active': string; readonly 'option.height': string; readonly 'option.padding-block': string; readonly 'option.font-size': string; readonly 'option.line-height': string; readonly 'root.transition': string; readonly 'root.opacity-disabled': string; readonly 'root.touch.min-height': string; readonly 'root.outline-width-focus': string; readonly 'root.outline-offset-focus': string; readonly 'root.border-width': string; readonly 'root.border-style': string; readonly 'root.border-color': string; readonly 'root.border-color-hover': string; readonly 'root.border-color-focus': string; readonly 'root.border-color-error': string; readonly 'root.border-color-warning': string; readonly 'root.border-radius': string; readonly 'root.background-color': string; readonly 'root.background-color-disabled': string; readonly 'root.box-shadow': string; readonly 'root.box-shadow-focus': string; readonly 'root.validation.box-shadow-error': string; readonly 'root.validation.box-shadow-warning': string; readonly 'root.color-loading': string; readonly 'motion.interaction.transition': string; readonly 'motion.spatial.transition': string; readonly 'text.color': string; readonly 'text.color-placeholder': string; readonly 'text.color-disabled': string; readonly 'tag.background-color': string; readonly 'tag.color': string; readonly 'tag.close.color-hover': string; readonly 'clear.color': string; readonly 'clear.color-hover': string; readonly 'clear.width': string; readonly 'clear.height': string; readonly 'clear.touch.width': string; readonly 'clear.box-shadow-focus': string; readonly 'icon.width': string; readonly 'icon.color': string; readonly 'field.gap': string; readonly 'field.padding-inline': string; readonly 'field.label.top': string; readonly 'field.label.padding-inline': string; readonly 'field.label.font-size': string; readonly 'field.label.line-height': string; readonly 'field.label.color': string; readonly 'field.label.color-focus': string; readonly 'field.input.padding-top': string; readonly 'field.transition': string; readonly 'field.filled.background-color': string; readonly 'field.filled.background-color-hover': string; readonly 'field.indicator.border-color': string; readonly 'field.indicator.box-shadow-focus': string; readonly 'field.indicator.box-shadow-error': string; readonly 'field.indicator.box-shadow-warning': string; readonly 'field.color-error': string; readonly 'field.color-warning': string; readonly 'field.support.line-height': string; readonly 'group.color': string; readonly 'group.font-size': string; readonly 'size.large.height': string; readonly 'size.large.padding': string; readonly 'size.large.font-size': string; readonly 'size.large.line-height': string; readonly 'size.large.action.width': string; readonly 'size.middle.height': string; readonly 'size.middle.padding': string; readonly 'size.middle.font-size': string; readonly 'size.middle.line-height': string; readonly 'size.middle.action.width': string; readonly 'size.small.height': string; readonly 'size.small.padding': string; readonly 'size.small.font-size': string; readonly 'size.small.line-height': string; readonly 'size.small.action.width': string; readonly 'dropdown.margin': string; readonly 'dropdown.max-height': string; readonly 'dropdown.padding': string; readonly 'dropdown.option.padding': string; readonly 'dropdown.border-radius': string; };
2
- declare const token: { readonly 'option': { readonly 'checkbox': { readonly 'width': string; }; readonly 'background-color-hover': string; readonly 'color-selected': string; readonly 'background-color-selected': string; readonly 'color-disabled': string; readonly 'highlight': { readonly 'background-color': string; }; readonly 'background-color-active': string; readonly 'height': string; readonly 'padding-block': string; readonly 'font-size': string; readonly 'line-height': string; }; readonly 'root': { readonly 'transition': string; readonly 'opacity-disabled': string; readonly 'touch': { readonly 'min-height': string; }; readonly 'outline-width-focus': string; readonly 'outline-offset-focus': string; readonly 'border-width': string; readonly 'border-style': string; readonly 'border-color': string; readonly 'border-color-hover': string; readonly 'border-color-focus': string; readonly 'border-color-error': string; readonly 'border-color-warning': string; readonly 'border-radius': string; readonly 'background-color': string; readonly 'background-color-disabled': string; readonly 'box-shadow': string; readonly 'box-shadow-focus': string; readonly 'validation': { readonly 'box-shadow-error': string; readonly 'box-shadow-warning': string; }; readonly 'color-loading': string; }; readonly 'motion': { readonly 'interaction': { readonly 'transition': string; }; readonly 'spatial': { readonly 'transition': string; }; }; readonly 'text': { readonly 'color': string; readonly 'color-placeholder': string; readonly 'color-disabled': string; }; readonly 'tag': { readonly 'background-color': string; readonly 'color': string; readonly 'close': { readonly 'color-hover': string; }; }; readonly 'clear': { readonly 'color': string; readonly 'color-hover': string; readonly 'width': string; readonly 'height': string; readonly 'touch': { readonly 'width': string; }; readonly 'box-shadow-focus': string; }; readonly 'icon': { readonly 'width': string; readonly 'color': string; }; readonly 'field': { readonly 'gap': string; readonly 'padding-inline': string; readonly 'label': { readonly 'top': string; readonly 'padding-inline': string; readonly 'font-size': string; readonly 'line-height': string; readonly 'color': string; readonly 'color-focus': string; }; readonly 'input': { readonly 'padding-top': string; }; readonly 'transition': string; readonly 'filled': { readonly 'background-color': string; readonly 'background-color-hover': string; }; readonly 'indicator': { readonly 'border-color': string; readonly 'box-shadow-focus': string; readonly 'box-shadow-error': string; readonly 'box-shadow-warning': string; }; readonly 'color-error': string; readonly 'color-warning': string; readonly 'support': { readonly 'line-height': string; }; }; readonly 'group': { readonly 'color': string; readonly 'font-size': string; }; readonly 'size': { readonly 'large': { readonly 'height': string; readonly 'padding': string; readonly 'font-size': string; readonly 'line-height': string; readonly 'action': { readonly 'width': string; }; }; readonly 'middle': { readonly 'height': string; readonly 'padding': string; readonly 'font-size': string; readonly 'line-height': string; readonly 'action': { readonly 'width': string; }; }; readonly 'small': { readonly 'height': string; readonly 'padding': string; readonly 'font-size': string; readonly 'line-height': string; readonly 'action': { readonly 'width': string; }; }; }; readonly 'dropdown': { readonly 'margin': string; readonly 'max-height': string; readonly 'padding': string; readonly 'option': { readonly 'padding': string; }; readonly 'border-radius': string; }; };
1
+ export declare const vars: { readonly 'option.checkbox.width': string; readonly 'option.background-color-hover': string; readonly 'option.color-selected': string; readonly 'option.background-color-selected': string; readonly 'option.border-radius': string; readonly 'option.color-disabled': string; readonly 'option.highlight.background-color': string; readonly 'option.background-color-active': string; readonly 'option.selection.background-color-hover': string; readonly 'option.selection.background-color-active': string; readonly 'option.activity.outline-color': string; readonly 'option.activity.outline-width': string; readonly 'option.activity.outline-offset': string; readonly 'option.height': string; readonly 'option.padding-block': string; readonly 'option.font-size': string; readonly 'option.line-height': string; readonly 'root.transition': string; readonly 'root.opacity-disabled': string; readonly 'root.touch.min-height': string; readonly 'root.outline-width-focus': string; readonly 'root.outline-offset-focus': string; readonly 'root.border-width': string; readonly 'root.border-style': string; readonly 'root.border-color': string; readonly 'root.border-color-hover': string; readonly 'root.border-color-focus': string; readonly 'root.border-color-error': string; readonly 'root.border-color-warning': string; readonly 'root.border-radius': string; readonly 'root.background-color': string; readonly 'root.background-color-disabled': string; readonly 'root.box-shadow': string; readonly 'root.box-shadow-focus': string; readonly 'root.validation.box-shadow-error': string; readonly 'root.validation.box-shadow-warning': string; readonly 'root.color-loading': string; readonly 'motion.interaction.transition': string; readonly 'motion.spatial.transition': string; readonly 'text.color': string; readonly 'text.color-placeholder': string; readonly 'text.color-disabled': string; readonly 'tag.background-color': string; readonly 'tag.color': string; readonly 'tag.close.color-hover': string; readonly 'clear.color': string; readonly 'clear.color-hover': string; readonly 'clear.width': string; readonly 'clear.height': string; readonly 'clear.touch.width': string; readonly 'clear.box-shadow-focus': string; readonly 'icon.width': string; readonly 'icon.color': string; readonly 'field.gap': string; readonly 'field.padding-inline': string; readonly 'field.min-height': string; readonly 'field.label.top': string; readonly 'field.label.padding-inline': string; readonly 'field.label.font-size': string; readonly 'field.label.line-height': string; readonly 'field.label.color': string; readonly 'field.label.color-focus': string; readonly 'field.input.padding-top': string; readonly 'field.transition': string; readonly 'field.filled.background-color': string; readonly 'field.filled.background-color-hover': string; readonly 'field.filled.min-height': string; readonly 'field.indicator.border-color': string; readonly 'field.indicator.box-shadow-focus': string; readonly 'field.indicator.box-shadow-error': string; readonly 'field.indicator.box-shadow-warning': string; readonly 'field.color-error': string; readonly 'field.color-warning': string; readonly 'field.support.line-height': string; readonly 'field.outline.border-width-focus': string; readonly 'group.color': string; readonly 'group.font-size': string; readonly 'size.large.height': string; readonly 'size.large.padding': string; readonly 'size.large.font-size': string; readonly 'size.large.line-height': string; readonly 'size.large.action.width': string; readonly 'size.medium.height': string; readonly 'size.medium.padding': string; readonly 'size.medium.font-size': string; readonly 'size.medium.line-height': string; readonly 'size.medium.action.width': string; readonly 'size.small.height': string; readonly 'size.small.padding': string; readonly 'size.small.font-size': string; readonly 'size.small.line-height': string; readonly 'size.small.action.width': string; readonly 'dropdown.margin': string; readonly 'dropdown.max-height': string; readonly 'dropdown.padding': string; readonly 'dropdown.option.padding': string; readonly 'dropdown.border-radius': string; };
2
+ declare const token: { readonly 'option': { readonly 'checkbox': { readonly 'width': string; }; readonly 'background-color-hover': string; readonly 'color-selected': string; readonly 'background-color-selected': string; readonly 'border-radius': string; readonly 'color-disabled': string; readonly 'highlight': { readonly 'background-color': string; }; readonly 'background-color-active': string; readonly 'selection': { readonly 'background-color-hover': string; readonly 'background-color-active': string; }; readonly 'activity': { readonly 'outline-color': string; readonly 'outline-width': string; readonly 'outline-offset': string; }; readonly 'height': string; readonly 'padding-block': string; readonly 'font-size': string; readonly 'line-height': string; }; readonly 'root': { readonly 'transition': string; readonly 'opacity-disabled': string; readonly 'touch': { readonly 'min-height': string; }; readonly 'outline-width-focus': string; readonly 'outline-offset-focus': string; readonly 'border-width': string; readonly 'border-style': string; readonly 'border-color': string; readonly 'border-color-hover': string; readonly 'border-color-focus': string; readonly 'border-color-error': string; readonly 'border-color-warning': string; readonly 'border-radius': string; readonly 'background-color': string; readonly 'background-color-disabled': string; readonly 'box-shadow': string; readonly 'box-shadow-focus': string; readonly 'validation': { readonly 'box-shadow-error': string; readonly 'box-shadow-warning': string; }; readonly 'color-loading': string; }; readonly 'motion': { readonly 'interaction': { readonly 'transition': string; }; readonly 'spatial': { readonly 'transition': string; }; }; readonly 'text': { readonly 'color': string; readonly 'color-placeholder': string; readonly 'color-disabled': string; }; readonly 'tag': { readonly 'background-color': string; readonly 'color': string; readonly 'close': { readonly 'color-hover': string; }; }; readonly 'clear': { readonly 'color': string; readonly 'color-hover': string; readonly 'width': string; readonly 'height': string; readonly 'touch': { readonly 'width': string; }; readonly 'box-shadow-focus': string; }; readonly 'icon': { readonly 'width': string; readonly 'color': string; }; readonly 'field': { readonly 'gap': string; readonly 'padding-inline': string; readonly 'min-height': string; readonly 'label': { readonly 'top': string; readonly 'padding-inline': string; readonly 'font-size': string; readonly 'line-height': string; readonly 'color': string; readonly 'color-focus': string; }; readonly 'input': { readonly 'padding-top': string; }; readonly 'transition': string; readonly 'filled': { readonly 'background-color': string; readonly 'background-color-hover': string; readonly 'min-height': string; }; readonly 'indicator': { readonly 'border-color': string; readonly 'box-shadow-focus': string; readonly 'box-shadow-error': string; readonly 'box-shadow-warning': string; }; readonly 'color-error': string; readonly 'color-warning': string; readonly 'support': { readonly 'line-height': string; }; readonly 'outline': { readonly 'border-width-focus': string; }; }; readonly 'group': { readonly 'color': string; readonly 'font-size': string; }; readonly 'size': { readonly 'large': { readonly 'height': string; readonly 'padding': string; readonly 'font-size': string; readonly 'line-height': string; readonly 'action': { readonly 'width': string; }; }; readonly 'medium': { readonly 'height': string; readonly 'padding': string; readonly 'font-size': string; readonly 'line-height': string; readonly 'action': { readonly 'width': string; }; }; readonly 'small': { readonly 'height': string; readonly 'padding': string; readonly 'font-size': string; readonly 'line-height': string; readonly 'action': { readonly 'width': string; }; }; }; readonly 'dropdown': { readonly 'margin': string; readonly 'max-height': string; readonly 'padding': string; readonly 'option': { readonly 'padding': string; }; readonly 'border-radius': string; }; };
3
3
  export default token;
@@ -1,4 +1,6 @@
1
- import type { HTMLAttributes, ReactNode, Ref } from "react";
1
+ import type { HTMLAttributes, ReactNode, Ref, SyntheticEvent } from "react";
2
+ import type { DropdownOpenChangeDetails } from '@crab-dev/rc-dropdown-container';
3
+ import type { ControlledValueProps, ControlledOpenProps } from '@crab-dev/rc-hooks';
2
4
  export interface SelectOption {
3
5
  label: ReactNode;
4
6
  value: string;
@@ -15,7 +17,7 @@ export interface FlatOption {
15
17
  disabled?: boolean;
16
18
  isGroupLabel?: boolean;
17
19
  }
18
- interface BaseSelectProps extends Omit<HTMLAttributes<HTMLDivElement>, "onChange" | "defaultValue"> {
20
+ interface BaseSelectProps extends Omit<HTMLAttributes<HTMLDivElement>, "defaultValue"> {
19
21
  // 字段外观,默认 outlined
20
22
  appearance?: "outlined" | "filled";
21
23
  // 可见的浮动字段标签
@@ -36,8 +38,8 @@ interface BaseSelectProps extends Omit<HTMLAttributes<HTMLDivElement>, "onChange
36
38
  disabled?: boolean;
37
39
  // 是否可搜索(显示输入框以过滤选项)
38
40
  searchable?: boolean;
39
- // 组件尺寸;省略时继承 ConfigProvider,未配置时为 middle
40
- size?: "large" | "middle" | "small";
41
+ // 组件尺寸;省略时继承 ConfigProvider,未配置时为 medium
42
+ size?: "large" | "medium" | "small";
41
43
  // 状态:用于显示错误或警告的边框样式
42
44
  status?: "error" | "warning";
43
45
  // 是否允许清除;有已选项且未禁用时,悬停字段或聚焦尾部按钮会将箭头切换为清除
@@ -57,25 +59,21 @@ interface BaseSelectProps extends Omit<HTMLAttributes<HTMLDivElement>, "onChange
57
59
  // 自定义选项渲染函数,提供当前选项及其选中状态
58
60
  optionRender?: (option: SelectOption, info: { selected: boolean }) => ReactNode;
59
61
  // 自定义 tag 渲染(多选时)
60
- tagRender?: (option: SelectOption, onClose: () => void) => ReactNode;
62
+ tagRender?: (option: SelectOption, onClose: (event?: SyntheticEvent<HTMLElement>) => void) => ReactNode;
61
63
  // 自定义下拉菜单整体渲染(包裹原始菜单)
62
64
  dropdownRender?: (menu: ReactNode) => ReactNode;
63
- // 下拉打开/关闭回调
64
- onOpenChange?: (open: boolean) => void;
65
- // 聚焦与失焦回调
66
- onFocus?: () => void;
67
- onBlur?: () => void;
68
65
  }
69
- export interface SingleSelectProps extends BaseSelectProps {
66
+ export type SingleSelectProps = BaseSelectProps & ControlledValueProps<string | null, SelectChangeDetails> & ControlledOpenProps<DropdownOpenChangeDetails> & {
70
67
  multiple?: false;
71
- value?: string | null;
72
- defaultValue?: string | null;
73
- onChange?: (value: string | null, option: SelectOption | undefined) => void;
74
- }
75
- export interface MultiSelectProps extends BaseSelectProps {
68
+ };
69
+ export type MultiSelectProps = BaseSelectProps & ControlledValueProps<string[], SelectChangeDetails> & ControlledOpenProps<DropdownOpenChangeDetails> & {
76
70
  multiple: true;
77
- value?: string[];
78
- defaultValue?: string[];
79
- onChange?: (value: string[], options: SelectOption[]) => void;
80
- }
71
+ };
81
72
  export type SelectProps = SingleSelectProps | MultiSelectProps;
73
+ export interface SelectChangeDetails {
74
+ reason: 'select' | 'deselect' | 'clear' | 'remove';
75
+ options: readonly SelectOption[];
76
+ option?: SelectOption;
77
+ /** 由用户交互触发时保留原事件;自定义标签也可主动调用无事件的关闭命令。 */
78
+ event?: SyntheticEvent<HTMLElement>;
79
+ }
@@ -1,4 +1,4 @@
1
1
  import Select from './_wake/src/select.js';
2
- export type { SelectOption, SelectOptionGroup, SelectOptionOrGroup, SelectProps } from './_wake/src/types.js';
2
+ export type { SelectOption, SelectOptionGroup, SelectOptionOrGroup, SelectProps, SelectChangeDetails } from './_wake/src/types.js';
3
3
  export default Select;
4
4
  export { vars as TokenVars } from './_wake/src/token.js';
@@ -1,7 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useComponentSize } from "@crab-dev/rc-config-provider";
2
+ import { useComponentSize, useLocale } from "@crab-dev/rc-config-provider";
3
3
  import RcDropdownContainer from "@crab-dev/rc-dropdown-container";
4
+ import { useDropdownPositionReference } from "@crab-dev/rc-dropdown-container/internal";
5
+ import semanticToken from "@crab-dev/rc-token-semantic";
4
6
  import { useId, useRef, useState } from "react";
7
+ ;
5
8
  import { useControllableValue } from "@crab-dev/rc-hooks";
6
9
  import useKeyboardNavigation from "./hooks/useKeyboardNavigation.mjs";
7
10
  import SelectInput from "./selectInput.mjs";
@@ -60,8 +63,13 @@ const filterOptions = (flatOptions, searchable, searchText) => {
60
63
  const firstEnabledIndex = (flatOptions) => flatOptions.findIndex((opt) => !opt.disabled && !opt.isGroupLabel);
61
64
  const fieldStyle = "rc-select-0f3ecf945b98";
62
65
  const supportingStyle = "rc-select-3bd2a27273ad";
63
- const Select = ({ ref, id, options, placeholder = "请选择", disabled = false, searchable = false, multiple = false, size: sizeProp, status, appearance = "outlined", label, supportingText, errorText, required, allowClear = false, loading = false, maxTagCount, autoFocus = false, notFoundContent, popupClassName, popupMatchSelectWidth = true, optionRender, tagRender, dropdownRender, value, defaultValue, onChange, onOpenChange, onFocus, onBlur, className, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, ...restProps }) => {
66
+ function SelectField({ children }) {
67
+ const setPositionReference = useDropdownPositionReference();
68
+ return _jsx("div", { className: fieldStyle, ref: setPositionReference, children: children });
69
+ }
70
+ function Select({ ref, id, options, placeholder, disabled = false, searchable = false, multiple = false, size: sizeProp, status, appearance = "outlined", label, supportingText, errorText, required, allowClear = false, loading = false, maxTagCount, autoFocus = false, notFoundContent, popupClassName, popupMatchSelectWidth = true, optionRender, tagRender, dropdownRender, value, defaultValue, onValueChange, open, defaultOpen, onOpenChange, onFocus, onBlur, className, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, ...restProps }) {
64
71
  const size = useComponentSize(sizeProp);
72
+ const { messages } = useLocale();
65
73
  const listboxId = useId();
66
74
  const generatedId = useId();
67
75
  const inputId = id ?? generatedId;
@@ -72,14 +80,13 @@ const Select = ({ ref, id, options, placeholder = "请选择", disabled = false,
72
80
  const [searchText, setSearchText] = useState("");
73
81
  const [selectedValues, setSelectedValues] = useControllableValue({ value: value !== undefined ? asArray(value) : undefined, defaultValue: asArray(defaultValue) });
74
82
  const [triggerWidth, setTriggerWidth] = useState(0);
75
- const onOpenChangeRef = useRef(onOpenChange);
76
- onOpenChangeRef.current = onOpenChange;
83
+ const controlRef = useRef(null);
77
84
  const allPlainOptions = flattenPlainOptions(options);
78
85
  const selectedOptions = allPlainOptions.filter((opt) => selectedValues.includes(opt.value));
79
86
  const flatOptions = flattenOptions(options);
80
87
  const filteredOptions = filterOptions(flatOptions, searchable, searchText);
81
88
  const { highlightIndex, highlightedOption, moveHighlight, resetHighlight, setHighlightIndex } = useKeyboardNavigation({ filteredOptions, open: true });
82
- const emitOpenChange = (nextOpen) => {
89
+ const syncOpenState = (nextOpen) => {
83
90
  if (nextOpen) {
84
91
  if (!multiple && selectedValues.length > 0) {
85
92
  const selectedIndex = filteredOptions.findIndex((opt) => opt.value === selectedValues[0]);
@@ -89,47 +96,56 @@ const Select = ({ ref, id, options, placeholder = "请选择", disabled = false,
89
96
  setSearchText("");
90
97
  resetHighlight();
91
98
  }
92
- onOpenChangeRef.current?.(nextOpen);
93
99
  };
94
- const updateValue = (nextValues) => {
100
+ const emitOpenChange = (nextOpen, details) => {
101
+ onOpenChange?.(nextOpen, details);
102
+ };
103
+ const openProps = open === undefined ? { defaultOpen } : { open };
104
+ const updateValue = (nextValues, reason, event) => {
105
+ if (disabled) return;
106
+ if (nextValues.length === selectedValues.length && nextValues.every((item, index) => item === selectedValues[index])) return;
95
107
  setSelectedValues(nextValues);
96
108
  if (multiple) {
97
109
  const nextOptions = allPlainOptions.filter((opt) => nextValues.includes(opt.value));
98
- onChange?.(nextValues, nextOptions);
110
+ onValueChange?.(nextValues, { reason, options: nextOptions, event });
99
111
  return;
100
112
  }
101
113
  const nextValue = nextValues[0] ?? null;
102
114
  const nextOption = allPlainOptions.find((opt) => opt.value === nextValue);
103
- onChange?.(nextValue, nextOption);
115
+ onValueChange?.(nextValue, { reason, option: nextOption, options: nextOption ? [nextOption] : [], event });
104
116
  };
105
- const handleOptionSelect = (option) => {
106
- if (option.disabled || option.isGroupLabel) {
117
+ const handleOptionSelect = (option, event) => {
118
+ if (disabled || option.disabled || option.isGroupLabel) {
107
119
  return;
108
120
  }
109
121
  if (multiple) {
110
122
  const exists = selectedValues.includes(option.value);
111
123
  const nextValues = exists ? selectedValues.filter((v) => v !== option.value) : [...selectedValues, option.value];
112
- updateValue(nextValues);
124
+ updateValue(nextValues, exists ? "deselect" : "select", event);
113
125
  return;
114
126
  }
115
- updateValue([option.value]);
127
+ controlRef.current?.focus();
128
+ updateValue([option.value], "select", event);
116
129
  };
117
- const handleClear = () => {
118
- updateValue([]);
130
+ const handleClear = (event) => {
131
+ if (disabled) return;
132
+ updateValue([], "clear", event);
119
133
  setSearchText("");
120
134
  };
121
- const handleRemoveTag = (val) => {
122
- updateValue(selectedValues.filter((v) => v !== val));
135
+ const handleRemoveTag = (val, event) => {
136
+ updateValue(selectedValues.filter((v) => v !== val), "remove", event);
123
137
  };
124
138
  const handleTriggerWidthChange = (width) => {
125
139
  setTriggerWidth(width);
126
140
  };
127
141
  const handleSearchTextChange = (text) => {
142
+ if (disabled) return;
128
143
  setSearchText(text);
129
144
  const nextFiltered = filterOptions(flatOptions, searchable, text);
130
145
  setHighlightIndex(firstEnabledIndex(nextFiltered));
131
146
  };
132
147
  const floatingStyle = popupMatchSelectWidth ? { width: triggerWidth } : { minWidth: triggerWidth };
133
- return _jsx(RcDropdownContainer, { size: size, ...restProps, className: className, overlay: _jsx(SelectOverlay, { multiple: multiple, loading: loading, filteredOptions: filteredOptions, selectedValues: selectedValues, triggerWidth: triggerWidth, highlightIndex: highlightIndex, listboxId: listboxId, notFoundContent: notFoundContent, popupClassName: popupClassName, optionRender: optionRender, dropdownRender: dropdownRender, onOptionSelect: handleOptionSelect, onOpenChange: emitOpenChange, onHighlightChange: setHighlightIndex }), floatingContainerProps: { style: floatingStyle }, children: _jsxs("div", { className: fieldStyle, children: [_jsx(SelectInput, { ref: ref, id: inputId, ariaLabel: ariaLabel, ariaLabelledBy: ariaLabelledBy ?? (label ? labelId : undefined), ariaDescribedBy: [ariaDescribedBy, description ? descriptionId : undefined].filter(Boolean).join(" ") || undefined, disabled: disabled, searchable: searchable, multiple: multiple, size: size, status: fieldStatus, appearance: appearance, label: label, labelId: labelId, required: required, allowClear: allowClear, loading: loading, maxTagCount: maxTagCount, autoFocus: autoFocus, placeholder: placeholder, selectedOptions: selectedOptions, searchText: searchText, highlightIndex: highlightIndex, highlightedOption: highlightedOption, listboxId: listboxId, tagRender: tagRender, onSearchTextChange: handleSearchTextChange, onOpenChange: emitOpenChange, onWidthChange: handleTriggerWidthChange, onMoveHighlight: moveHighlight, onSelectHighlighted: handleOptionSelect, onClear: handleClear, onRemoveTag: handleRemoveTag, onFocus: onFocus, onBlur: onBlur }), description && _jsx("div", { id: descriptionId, className: supportingStyle, "data-status": fieldStatus, children: description })] }) });
134
- };
148
+ return _jsx(RcDropdownContainer, { ...openProps, onOpenChange: emitOpenChange, size: size, ...restProps, className: className, overlay: _jsx(SelectOverlay, { size: size, multiple: multiple, disabled: disabled, loading: loading, filteredOptions: filteredOptions, selectedValues: selectedValues, triggerWidth: triggerWidth, highlightIndex: highlightIndex, listboxId: listboxId, notFoundContent: notFoundContent, popupClassName: popupClassName, optionRender: optionRender, dropdownRender: dropdownRender, onOptionSelect: handleOptionSelect, onHighlightChange: setHighlightIndex }), floatingContainerProps: { style: floatingStyle }, children: _jsxs(SelectField, { children: [_jsx(SelectInput, { ref: ref, controlRef: controlRef, id: inputId, ariaLabel: ariaLabel, ariaLabelledBy: ariaLabelledBy ?? (label ? labelId : undefined), ariaDescribedBy: [ariaDescribedBy, description ? descriptionId : undefined].filter(Boolean).join(" ") || undefined, disabled: disabled, searchable: searchable, multiple: multiple, size: size, status: fieldStatus, appearance: appearance, label: label, labelId: labelId, required: required, allowClear: allowClear, loading: loading, maxTagCount: maxTagCount, autoFocus: autoFocus, placeholder: placeholder ?? messages.select.placeholder, selectedOptions: selectedOptions, searchText: searchText, highlightIndex: highlightIndex, highlightedOption: highlightedOption, listboxId: listboxId, tagRender: tagRender, onSearchTextChange: handleSearchTextChange, onOpenStateChange: syncOpenState, onWidthChange: handleTriggerWidthChange, onMoveHighlight: moveHighlight, onSelectHighlighted: handleOptionSelect, onClear: handleClear, onRemoveTag: handleRemoveTag, onFocus: onFocus, onBlur: onBlur }), description && _jsx("div", { id: descriptionId, className: supportingStyle, "data-status": fieldStatus, role: errorText ? "alert" : undefined, children: description })] }) });
149
+ }
150
+ ;
135
151
  export default Select;
@@ -1,13 +1,15 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useDropdownContext } from "@crab-dev/rc-dropdown-container";
3
3
  import RcLineEdit from "@crab-dev/rc-line-edit";
4
- import Button, { TokenVars as buttonVars } from "@crab-dev/rc-button";
5
- import { SpinIndicator, TokenVars as spinVars } from "@crab-dev/rc-spin";
4
+ import Button from "@crab-dev/rc-button";
5
+ import { SpinIndicator } from "@crab-dev/rc-spin";
6
6
  import Tag from "@crab-dev/rc-tag";
7
7
  import { cx } from "@crab-dev/css";
8
- import { useCallback, useEffect, useRef, useState } from "react";
8
+ import { useConfig, useLocale } from "@crab-dev/rc-config-provider";
9
+ import { useCallback, useEffect, useEffectEvent, useRef, useState } from "react";
9
10
  import token, { vars } from "./token.mjs";
10
11
  ;
12
+ import { controlStyle, fieldControlStyle, fieldLayoutStyle, md3LabelMotionStyle, controlDisabledStyle, controlErrorStyle, controlWarningStyle, valueWrapStyle, singleValueStyle, placeholderStyle, searchInputStyle, caretStyle, caretOpenStyle, tagLabelStyle, suffixWrapStyle, actionStyle, loadingIconStyle } from "./selectInput.styles.mjs";
11
13
  const setRef = (ref, node) => {
12
14
  if (typeof ref === "function") {
13
15
  ref(node);
@@ -15,31 +17,16 @@ const setRef = (ref, node) => {
15
17
  ref.current = node;
16
18
  }
17
19
  };
18
- const controlStyle = "rc-select-208f8e064cef";
19
- const materialFieldStyle = "rc-select-de4f87d60f05";
20
- const fieldLabelStyle = "rc-select-608ca49a02dc";
21
- const controlDisabledStyle = "rc-select-238e884e6a17";
22
- const controlErrorStyle = "rc-select-e0726e9ad481";
23
- const controlWarningStyle = "rc-select-79b739b32fcb";
24
- const valueWrapStyle = "rc-select-1356dfc74a7f";
25
- const singleValueStyle = "rc-select-52cbc5cc4f0f";
26
- const placeholderStyle = "rc-select-37ce25dfcd13";
27
- const searchInputStyle = "rc-select-50dd3c70bcdc";
28
- const caretStyle = "rc-select-d7edabfe7361";
29
- const caretOpenStyle = "rc-select-30173f5e3d41";
30
- const tagLabelStyle = "rc-select-2e4337f305d8";
31
- const suffixWrapStyle = "rc-select-ee9f2054c459";
32
- const actionStyle = "rc-select-8504d95b52bc";
33
- const loadingIconStyle = "rc-select-3ab2c2f8c427";
34
- const sizeMetricsMap = { large: "rc-select-df4a743b55e1", middle: "rc-select-de4a743b542e", small: "rc-select-dd4a743b527b" };
35
- const sizeHeightFixedMap = { large: "rc-select-b8fc5925a597", middle: "rc-select-b7fc5925a3e4", small: "rc-select-bafc5925a8fd" };
36
- const sizeHeightFlexibleMap = { large: "rc-select-5d07f79a2f4a", middle: "rc-select-5e07f79a30fd", small: "rc-select-5b07f79a2be4" };
20
+ const sizeMetricsMap = { large: "rc-select-df4a743b55e1", medium: "rc-select-de4a743b542e", small: "rc-select-dd4a743b527b" };
21
+ const sizeHeightFixedMap = { large: "rc-select-b8fc5925a597", medium: "rc-select-b7fc5925a3e4", small: "rc-select-bafc5925a8fd" };
22
+ const sizeHeightFlexibleMap = { large: "rc-select-5d07f79a2f4a", medium: "rc-select-5e07f79a30fd", small: "rc-select-5b07f79a2be4" };
37
23
  const CaretIcon = () => _jsx("svg", { "aria-hidden": "true", focusable: "false", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "m7 10 5 5 5-5z" }) });
38
24
  const ClearIcon = () => _jsx("svg", { "aria-hidden": "true", focusable: "false", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "m18 6-12 12M6 6l12 12" }) });
39
25
  ;
40
- const SelectInput = ({ ref, id, ariaLabel, ariaLabelledBy, ariaDescribedBy, disabled, searchable, multiple, size, status, appearance, label, labelId, required, allowClear, loading, maxTagCount, autoFocus, placeholder, selectedOptions, searchText, highlightIndex, highlightedOption, listboxId, tagRender, onSearchTextChange, onOpenChange, onWidthChange, onMoveHighlight, onSelectHighlighted, onClear, onRemoveTag, onFocus, onBlur }) => {
26
+ const SelectInput = ({ ref, controlRef, id, ariaLabel, ariaLabelledBy, ariaDescribedBy, disabled, searchable, multiple, size, status, appearance, label, labelId, required, allowClear, loading, maxTagCount, autoFocus, placeholder, selectedOptions, searchText, highlightIndex, highlightedOption, listboxId, tagRender, onSearchTextChange, onOpenStateChange, onWidthChange, onMoveHighlight, onSelectHighlighted, onClear, onRemoveTag, onFocus, onBlur }) => {
41
27
  const { state, refs, dispatch } = useDropdownContext();
42
- const controlRef = useRef(null);
28
+ const { messages } = useLocale();
29
+ const { designSystem } = useConfig();
43
30
  const inputRef = useRef(null);
44
31
  const open = state.open;
45
32
  const [hovered, setHovered] = useState(false);
@@ -53,15 +40,14 @@ const SelectInput = ({ ref, id, ariaLabel, ariaLabelledBy, ariaDescribedBy, disa
53
40
  media.addEventListener("change", update);
54
41
  return () => media.removeEventListener("change", update);
55
42
  }, []);
56
- const onOpenChangeRef = useRef(onOpenChange);
57
- onOpenChangeRef.current = onOpenChange;
43
+ const syncOpen = useEffectEvent(onOpenStateChange);
58
44
  const mergeRef = useCallback((node) => {
59
45
  controlRef.current = node;
60
46
  if (typeof refs.setReference === "function") {
61
47
  refs.setReference(node);
62
48
  }
63
49
  setRef(ref, node);
64
- }, [refs.setReference, ref]);
50
+ }, [refs.setReference, ref, controlRef]);
65
51
  useEffect(() => {
66
52
  const el = controlRef.current;
67
53
  if (!el) {
@@ -76,22 +62,17 @@ const SelectInput = ({ ref, id, ariaLabel, ariaLabelledBy, ariaDescribedBy, disa
76
62
  return () => observer.disconnect();
77
63
  }, [onWidthChange]);
78
64
  useEffect(() => {
79
- if (open && searchable) {
65
+ if (open && searchable && !disabled) {
80
66
  queueMicrotask(() => inputRef.current?.focus());
81
67
  }
82
- }, [open, searchable]);
68
+ }, [disabled, open, searchable]);
83
69
  useEffect(() => {
84
- if (autoFocus && controlRef.current) {
70
+ if (autoFocus && !disabled && controlRef.current) {
85
71
  controlRef.current.focus();
86
72
  }
87
- }, [autoFocus]);
88
- const isFirstOpenRender = useRef(true);
73
+ }, [autoFocus, disabled]);
89
74
  useEffect(() => {
90
- if (isFirstOpenRender.current) {
91
- isFirstOpenRender.current = false;
92
- return;
93
- }
94
- onOpenChangeRef.current(open);
75
+ syncOpen(open);
95
76
  }, [open]);
96
77
  const handleClick = () => {
97
78
  if (disabled) {
@@ -121,7 +102,7 @@ const SelectInput = ({ ref, id, ariaLabel, ariaLabelledBy, ariaDescribedBy, disa
121
102
  case "Enter":
122
103
  e.preventDefault();
123
104
  if (open && highlightedOption) {
124
- onSelectHighlighted(highlightedOption);
105
+ onSelectHighlighted(highlightedOption, e);
125
106
  if (!multiple) {
126
107
  dispatch({ type: "setOpen", payload: false });
127
108
  }
@@ -140,29 +121,30 @@ const SelectInput = ({ ref, id, ariaLabel, ariaLabelledBy, ariaDescribedBy, disa
140
121
  case "Escape":
141
122
  if (open) {
142
123
  e.preventDefault();
143
- dispatch({ type: "setOpen", payload: false });
124
+ controlRef.current?.focus();
125
+ dispatch({ type: "setOpen", payload: false, details: { reason: "escape", event: e.nativeEvent } });
144
126
  }
145
127
  break;
146
128
  case "Backspace":
147
129
  if (multiple && searchText === "" && selectedOptions.length > 0) {
148
130
  const lastOption = selectedOptions[selectedOptions.length - 1];
149
- onRemoveTag(lastOption.value);
131
+ onRemoveTag(lastOption.value, e);
150
132
  }
151
133
  break;
152
134
  default:
153
135
  break;
154
136
  }
155
137
  };
156
- const triggerClear = () => {
138
+ const triggerClear = (event) => {
157
139
  controlRef.current?.focus();
158
- onClear();
140
+ onClear(event);
159
141
  if (open) {
160
142
  dispatch({ type: "setOpen", payload: false });
161
143
  }
162
144
  };
163
145
  const handleClearClick = (e) => {
164
146
  e.stopPropagation();
165
- triggerClear();
147
+ triggerClear(e);
166
148
  };
167
149
  const handleClearKeyDown = (e) => {
168
150
  if (e.key === "Enter" || e.key === " ") {
@@ -172,11 +154,13 @@ const SelectInput = ({ ref, id, ariaLabel, ariaLabelledBy, ariaDescribedBy, disa
172
154
  const canClear = allowClear && !disabled && selectedOptions.length > 0;
173
155
  const showClear = canClear && (hovered || actionFocused || withoutHover);
174
156
  const renderTag = (opt) => {
175
- const handleClose = () => onRemoveTag(opt.value);
157
+ const handleClose = (event) => {
158
+ if (!disabled) onRemoveTag(opt.value, event);
159
+ };
176
160
  if (tagRender) {
177
161
  return _jsx("span", { children: tagRender(opt, handleClose) }, opt.value);
178
162
  }
179
- return _jsx(Tag, { size: "small", closable: true, closeAriaLabel: `Remove ${typeof opt.label === "string" ? opt.label : opt.value}`, onClose: handleClose, children: _jsx("span", { className: tagLabelStyle, children: opt.label }) }, opt.value);
163
+ return _jsx(Tag, { size: "small", closable: !disabled, closeAriaLabel: messages.select.removeTag(typeof opt.label === "string" ? opt.label : opt.value), onClose: handleClose, children: _jsx("span", { className: tagLabelStyle, children: opt.label }) }, opt.value);
180
164
  };
181
165
  const renderTags = () => {
182
166
  if (maxTagCount !== undefined && selectedOptions.length > maxTagCount) {
@@ -188,31 +172,31 @@ const SelectInput = ({ ref, id, ariaLabel, ariaLabelledBy, ariaDescribedBy, disa
188
172
  };
189
173
  const renderContent = () => {
190
174
  if (searchable && open) {
191
- return _jsxs(_Fragment, { children: [multiple && renderTags(), _jsx(RcLineEdit, { ref: inputRef, bordered: false, value: searchText, placeholder: multiple && selectedOptions.length > 0 ? "" : placeholder, onChange: (e) => onSearchTextChange(e.target.value), onClick: (e) => e.stopPropagation(), className: searchInputStyle })] });
175
+ return _jsxs(_Fragment, { children: [multiple && renderTags(), _jsx(RcLineEdit, { ref: inputRef, "aria-label": ariaLabel ?? label ?? placeholder, bordered: false, disabled: disabled, value: searchText, placeholder: multiple && selectedOptions.length > 0 ? "" : placeholder, onChange: (e) => onSearchTextChange(e.target.value), onClick: (e) => e.stopPropagation(), className: searchInputStyle })] });
192
176
  }
193
177
  if (multiple) {
194
178
  if (selectedOptions.length === 0) {
195
- return _jsx("span", { className: placeholderStyle, children: placeholder });
179
+ return _jsx("span", { "data-role": "select-placeholder", className: placeholderStyle, children: placeholder });
196
180
  }
197
181
  return renderTags();
198
182
  }
199
183
  if (selectedOptions.length === 0) {
200
- return _jsx("span", { className: placeholderStyle, children: placeholder });
184
+ return _jsx("span", { "data-role": "select-placeholder", className: placeholderStyle, children: placeholder });
201
185
  }
202
186
  return _jsx("span", { className: singleValueStyle, children: selectedOptions[0]?.label });
203
187
  };
204
188
  const activeDescendantId = open && highlightIndex >= 0 ? `${listboxId}-option-${highlightIndex}` : undefined;
205
- return _jsxs("div", { id: id, role: "combobox", "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-required": required || undefined, "aria-expanded": open, "aria-disabled": disabled, "aria-busy": loading || undefined, "aria-haspopup": "listbox", "aria-controls": open ? listboxId : undefined, "aria-activedescendant": activeDescendantId, "aria-invalid": status === "error" ? true : undefined, "data-appearance": appearance, "data-clearable": canClear, "data-labeled": Boolean(label), "data-floating": Boolean(open || selectedOptions.length > 0 || searchText), "data-status": status, tabIndex: disabled ? -1 : 0, ref: mergeRef, className: cx.call(undefined, controlStyle, sizeMetricsMap[size], multiple ? sizeHeightFlexibleMap[size] : sizeHeightFixedMap[size], disabled && controlDisabledStyle, status === "error" && controlErrorStyle, status === "warning" && controlWarningStyle, materialFieldStyle), onClick: handleClick, onKeyDown: handleKeyDown, onFocus: onFocus, onBlur: onBlur, onPointerOver: (event) => {
189
+ return _jsxs("div", { className: cx.call(undefined, fieldLayoutStyle, designSystem === "md3" && md3LabelMotionStyle), "data-labeled": Boolean(label), "data-appearance": appearance, "data-floating": Boolean(open || selectedOptions.length > 0 || searchText), "data-open": open, "data-status": status, "data-disabled": disabled || undefined, children: [label && _jsxs("span", { "data-role": "select-label", children: [_jsx("span", { id: labelId, children: label }), required && _jsx("span", { "data-required-marker": "", "aria-hidden": "true", children: "*" })] }), _jsxs("div", { id: id, role: "combobox", "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-required": required || undefined, "aria-expanded": open, "aria-disabled": disabled, "aria-busy": loading || undefined, "aria-haspopup": "listbox", "aria-controls": open ? listboxId : undefined, "aria-activedescendant": activeDescendantId, "aria-invalid": status === "error" ? true : undefined, "data-appearance": appearance, "data-clearable": canClear, "data-labeled": Boolean(label), "data-floating": Boolean(open || selectedOptions.length > 0 || searchText), "data-status": status, tabIndex: disabled ? -1 : 0, ref: mergeRef, className: cx.call(undefined, controlStyle, sizeMetricsMap[size], multiple ? sizeHeightFlexibleMap[size] : sizeHeightFixedMap[size], disabled && controlDisabledStyle, status === "error" && controlErrorStyle, status === "warning" && controlWarningStyle, fieldControlStyle), onClick: handleClick, onKeyDown: handleKeyDown, onFocus: onFocus, onBlur: onBlur, onPointerOver: (event) => {
206
190
  if (event.pointerType !== "touch") setHovered(true);
207
191
  }, onPointerOut: (event) => {
208
192
  if (!(event.relatedTarget instanceof Node) || !event.currentTarget.contains(event.relatedTarget)) {
209
193
  setHovered(false);
210
194
  }
211
- }, children: [label && _jsxs("span", { id: labelId, "data-role": "select-label", className: fieldLabelStyle, children: [label, required ? " *" : ""] }), _jsx("div", { "data-role": "select-value", className: valueWrapStyle, children: renderContent() }), _jsxs("span", { "data-role": "select-suffix", className: suffixWrapStyle, children: [loading ? _jsx("span", { "aria-hidden": "true", className: loadingIconStyle, children: _jsx(SpinIndicator, {}) }) : null, _jsx(Button, { "data-role": showClear ? "select-clear" : "select-caret", className: actionStyle, type: "button", appearance: "text", shape: "circle", size: size, disabled: disabled, "aria-label": showClear ? "Clear" : open ? "Close options" : "Open options", "aria-expanded": showClear ? undefined : open, icon: showClear ? _jsx(ClearIcon, {}) : _jsx("span", { "aria-hidden": "true", className: cx.call(undefined, caretStyle, open && caretOpenStyle), children: _jsx(CaretIcon, {}) }), onFocus: () => setActionFocused(true), onBlur: () => setActionFocused(false), onClick: (event) => {
195
+ }, children: [label && appearance === "outlined" && _jsx("fieldset", { "aria-hidden": "true", children: _jsxs("legend", { children: [label, required ? " *" : ""] }) }), _jsx("div", { "data-role": "select-value", className: valueWrapStyle, children: renderContent() }), _jsxs("span", { "data-role": "select-suffix", className: suffixWrapStyle, children: [loading ? _jsx("span", { "aria-hidden": "true", className: loadingIconStyle, children: _jsx(SpinIndicator, {}) }) : null, _jsx(Button, { "data-role": showClear ? "select-clear" : "select-caret", className: actionStyle, type: "button", tabIndex: canClear ? 0 : -1, appearance: "text", shape: "circle", size: size === "small" ? "xs" : size === "large" ? "m" : "s", disabled: disabled, "aria-label": showClear ? messages.common.clear : open ? messages.select.closeOptions : messages.select.openOptions, "aria-expanded": showClear ? undefined : open, icon: showClear ? _jsx(ClearIcon, {}) : _jsx("span", { "aria-hidden": "true", className: cx.call(undefined, caretStyle, open && caretOpenStyle), children: _jsx(CaretIcon, {}) }), onFocus: () => setActionFocused(true), onBlur: () => setActionFocused(false), onClick: (event) => {
212
196
  if (showClear) handleClearClick(event); else {
213
197
  event.stopPropagation();
214
198
  handleClick();
215
199
  }
216
- }, onKeyDown: handleClearKeyDown })] })] });
200
+ }, onKeyDown: handleClearKeyDown })] })] })] });
217
201
  };
218
202
  export default SelectInput;
@@ -0,0 +1,21 @@
1
+ import { TokenVars as buttonVars } from "@crab-dev/rc-button";
2
+ import { TokenVars as spinVars } from "@crab-dev/rc-spin";
3
+ import semanticToken from "@crab-dev/rc-token-semantic";
4
+ import token, { vars } from "./token.mjs";
5
+ export const controlStyle = "rc-select-5e896678dc89";
6
+ export const fieldControlStyle = "rc-select-29edc097b42b";
7
+ export const fieldLayoutStyle = "rc-select-910e459fbbe0";
8
+ export const md3LabelMotionStyle = "rc-select-1dddd2d985da";
9
+ export const controlDisabledStyle = "rc-select-82fbf3807561";
10
+ export const controlErrorStyle = "rc-select-6d2a034155e7";
11
+ export const controlWarningStyle = "rc-select-36466465840d";
12
+ export const valueWrapStyle = "rc-select-f0b8a9c0c369";
13
+ export const singleValueStyle = "rc-select-bcd33343d38d";
14
+ export const placeholderStyle = "rc-select-30719d992205";
15
+ export const searchInputStyle = "rc-select-c91210473c12";
16
+ export const caretStyle = "rc-select-8ca15a371517";
17
+ export const caretOpenStyle = "rc-select-72a72768a0ef";
18
+ export const tagLabelStyle = "rc-select-113a9493f812";
19
+ export const suffixWrapStyle = "rc-select-5032f5885db7";
20
+ export const actionStyle = "rc-select-aa51540f6a1a";
21
+ export const loadingIconStyle = "rc-select-14f1c2e4ccd5";
@@ -1,56 +1,50 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import Checkbox, { TokenVars as checkboxVars } from "@crab-dev/rc-checkbox";
3
- import { SpinIndicator, TokenVars as spinVars } from "@crab-dev/rc-spin";
2
+ import Checkbox from "@crab-dev/rc-checkbox";
3
+ import { componentDensityMetrics, useConfig, useLocale } from "@crab-dev/rc-config-provider";
4
+ import { SpinIndicator } from "@crab-dev/rc-spin";
4
5
  import RcVirtual from "@crab-dev/rc-virtual";
5
6
  import { useMediaQuery } from "@crab-dev/rc-hooks";
6
7
  import { cx } from "@crab-dev/css";
7
8
  import { useDropdownContext } from "@crab-dev/rc-dropdown-container";
8
9
  import { useEffect, useLayoutEffect, useRef, useState } from "react";
9
- import token from "./token.mjs";
10
10
  ;
11
+ import { overlayStyle, optionStyle, optionHighlightStyle, measureStyle, optionSelectedStyle, optionSelectedSingleStyle, optionDisabledStyle, optionLabelStyle, checkIconStyle, checkboxIndicatorStyle, emptyStyle, groupLabelStyle, groupedOptionStyle, virtualTopSpacerStyle, virtualBottomSpacerStyle, loadingStyle } from "./selectOverlay.styles.mjs";
11
12
  const hasGroups = (options) => options.some((opt) => opt.isGroupLabel);
12
- const overlayStyle = "rc-select-8032ea9d3fcc";
13
- const optionStyle = "rc-select-41a9c05e6999";
14
- const optionHighlightStyle = "rc-select-787681543371";
15
- const measureStyle = "rc-select-9fccdf1a17f8";
16
- const optionSelectedStyle = "rc-select-afbd832dfa96";
17
- const optionSelectedSingleStyle = "rc-select-92da46dc013a";
18
- const optionDisabledStyle = "rc-select-b51a97d11fd1";
19
- const optionLabelStyle = "rc-select-462ffe03d1f7";
20
- const checkIconStyle = "rc-select-4d7b259b317b";
21
- const checkboxIndicatorStyle = "rc-select-8509288b81d2";
22
- const emptyStyle = "rc-select-742133f23b03";
23
- const groupLabelStyle = "rc-select-4222a3b1548f";
24
- const groupedOptionStyle = "rc-select-fa0b905ad8a1";
25
- const virtualTopSpacerStyle = "rc-select-25c250ff4ce4";
26
- const virtualBottomSpacerStyle = "rc-select-bf5e0c73c29c";
27
- const loadingStyle = "rc-select-0ed119f292d0";
28
13
  const DROPDOWN_PADDING = 4;
29
- const ROW_HEIGHT = 48;
30
14
  const MAX_VIEWPORT_HEIGHT = 256;
31
15
  ;
32
- const SelectOverlay = ({ multiple, loading, filteredOptions, selectedValues, triggerWidth, highlightIndex, listboxId, notFoundContent, popupClassName, optionRender, dropdownRender, onOptionSelect, onOpenChange, onHighlightChange }) => {
16
+ const SelectOverlay = ({ size, multiple, disabled, loading, filteredOptions, selectedValues, triggerWidth, highlightIndex, listboxId, notFoundContent, popupClassName, optionRender, dropdownRender, onOptionSelect, onHighlightChange }) => {
33
17
  const { dispatch } = useDropdownContext();
34
18
  const coarsePointer = useMediaQuery("(pointer: coarse)");
35
19
  const overlayRef = useRef(null);
36
- const [measuredRowHeight, setMeasuredRowHeight] = useState(ROW_HEIGHT);
37
- const rowHeight = Math.max(coarsePointer ? 48 : ROW_HEIGHT, measuredRowHeight);
20
+ const [measuredRowHeight, setMeasuredRowHeight] = useState(0);
21
+ const [measuredContentWidth, setMeasuredContentWidth] = useState(0);
22
+ const { density } = useConfig();
23
+ const { messages } = useLocale();
24
+ const rowHeight = Math.max(coarsePointer ? 48 : componentDensityMetrics[density].item, measuredRowHeight);
38
25
  useLayoutEffect(() => {
39
- const option = overlayRef.current?.querySelector("[data-select-option-measure]");
40
- if (!option) return;
26
+ const overlay = overlayRef.current;
27
+ const option = overlay?.querySelector("[data-select-option-measure]");
28
+ if (!overlay || !option) return;
41
29
  const measure = () => {
42
30
  const height = option.getBoundingClientRect().height;
43
31
  if (height > 0) {
44
32
  overlayRef.current?.style.setProperty("--select-option-measured-height", `${height}px`);
45
33
  setMeasuredRowHeight(height);
46
34
  }
35
+ const style = overlay.ownerDocument.defaultView?.getComputedStyle(overlay);
36
+ const paddingStart = Number.parseFloat(style?.paddingLeft ?? "") || 0;
37
+ const paddingEnd = Number.parseFloat(style?.paddingRight ?? "") || 0;
38
+ const width = overlay.clientWidth - paddingStart - paddingEnd;
39
+ if (width > 0) setMeasuredContentWidth(width);
47
40
  };
48
41
  measure();
49
42
  if (typeof ResizeObserver === "undefined") return;
50
43
  const observer = new ResizeObserver(measure);
51
44
  observer.observe(option);
45
+ observer.observe(overlay);
52
46
  return () => observer.disconnect();
53
- }, [coarsePointer, filteredOptions.length, loading]);
47
+ }, [coarsePointer, density, filteredOptions.length, loading, size]);
54
48
  const gridRef = useRef(null);
55
49
  useEffect(() => {
56
50
  if (highlightIndex < 0) {
@@ -59,7 +53,7 @@ const SelectOverlay = ({ multiple, loading, filteredOptions, selectedValues, tri
59
53
  gridRef.current?.scrollToCell({ rowIndex: highlightIndex });
60
54
  }, [highlightIndex]);
61
55
  const rowCount = filteredOptions.length;
62
- const contentWidth = Math.max(triggerWidth - DROPDOWN_PADDING * 2, 160);
56
+ const contentWidth = measuredContentWidth || Math.max(triggerWidth - DROPDOWN_PADDING * 2, 160);
63
57
  const viewportHeight = Math.min(MAX_VIEWPORT_HEIGHT, rowCount * rowHeight);
64
58
  const rows = Array(rowCount).fill(rowHeight);
65
59
  const columns = [contentWidth];
@@ -67,7 +61,7 @@ const SelectOverlay = ({ multiple, loading, filteredOptions, selectedValues, tri
67
61
  return _jsx("div", { id: listboxId, role: "listbox", "aria-busy": "true", "data-select-overlay": true, className: cx.call(undefined, overlayStyle, popupClassName), children: _jsx("div", { className: loadingStyle, children: _jsx(SpinIndicator, {}) }) });
68
62
  }
69
63
  if (filteredOptions.length === 0) {
70
- return _jsx("div", { id: listboxId, role: "listbox", "data-select-overlay": true, className: cx.call(undefined, overlayStyle, popupClassName), children: _jsx("div", { className: emptyStyle, children: notFoundContent ?? "无匹配选项" }) });
64
+ return _jsx("div", { id: listboxId, role: "listbox", "data-select-overlay": true, className: cx.call(undefined, overlayStyle, popupClassName), children: _jsx("div", { className: emptyStyle, children: notFoundContent ?? messages.select.empty }) });
71
65
  }
72
66
  const isGrouped = hasGroups(filteredOptions);
73
67
  const menu = _jsx(RcVirtual, { gridRef: gridRef, viewportWidth: contentWidth, viewportHeight: viewportHeight, gridTemplateColumns: columns, gridTemplateRows: rows, overscanRowCount: 4, renderRows: (rowRange) => {
@@ -80,19 +74,21 @@ const SelectOverlay = ({ multiple, loading, filteredOptions, selectedValues, tri
80
74
  }
81
75
  const selected = selectedValues.includes(option.value);
82
76
  const highlighted = i === highlightIndex;
83
- const content = optionRender ? optionRender(option, { selected }) : _jsxs(_Fragment, { children: [multiple ? _jsx(Checkbox, { className: checkboxIndicatorStyle, checked: selected, disabled: option.disabled, tabIndex: -1, "aria-label": typeof option.label === "string" ? option.label : option.value }) : null, _jsx("span", { className: optionLabelStyle, children: option.label }), !multiple && selected ? _jsx("span", { className: checkIconStyle, children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "M20 6 9 17l-5-5" }) }) }) : null] });
77
+ const content = optionRender ? optionRender(option, { selected }) : _jsxs(_Fragment, { children: [multiple ? _jsx(Checkbox, { className: checkboxIndicatorStyle, checked: selected, disabled: disabled || option.disabled, tabIndex: -1, "aria-label": typeof option.label === "string" ? option.label : option.value }) : null, _jsx("span", { className: optionLabelStyle, children: option.label }), !multiple && selected ? _jsx("span", { className: checkIconStyle, children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "M20 6 9 17l-5-5" }) }) }) : null] });
84
78
  const optionAriaLabel = multiple ? typeof option.label === "string" ? option.label : option.value : undefined;
85
- nodes.push(_jsx("div", { id: `${listboxId}-option-${i}`, role: "option", "aria-selected": selected, "aria-disabled": option.disabled || undefined, "aria-label": optionAriaLabel, className: cx.call(undefined, optionStyle, isGrouped && groupedOptionStyle, selected && (multiple ? optionSelectedStyle : optionSelectedSingleStyle), highlighted && optionHighlightStyle, option.disabled && optionDisabledStyle), onMouseEnter: () => onHighlightChange(i), onClick: () => {
86
- onOptionSelect(option);
79
+ nodes.push(_jsx("div", { id: `${listboxId}-option-${i}`, role: "option", "aria-selected": selected, "aria-disabled": disabled || option.disabled || undefined, "data-highlighted": highlighted || undefined, "aria-label": optionAriaLabel, className: cx.call(undefined, optionStyle, isGrouped && groupedOptionStyle, selected && (multiple ? optionSelectedStyle : optionSelectedSingleStyle), highlighted && optionHighlightStyle, (disabled || option.disabled) && optionDisabledStyle), onMouseEnter: () => {
80
+ if (!disabled) onHighlightChange(i);
81
+ }, onClick: (event) => {
82
+ if (disabled) return;
83
+ onOptionSelect(option, event);
87
84
  if (!multiple && !option.disabled) {
88
85
  dispatch({ type: "setOpen", payload: false });
89
- onOpenChange(false);
90
86
  }
91
87
  }, children: content }, option.value));
92
88
  }
93
89
  nodes.push(_jsx("div", { "data-select-virtual-spacer": "bottom", className: virtualBottomSpacerStyle }, "__select-bottom-padding__"));
94
90
  return nodes;
95
91
  } });
96
- return _jsxs("div", { id: listboxId, ref: overlayRef, "data-select-overlay": true, className: cx.call(undefined, overlayStyle, popupClassName), role: "listbox", "aria-multiselectable": multiple || undefined, children: [_jsx("div", { "aria-hidden": "true", "data-select-option-measure": "", className: cx(optionStyle, measureStyle), children: "M" }), dropdownRender ? dropdownRender(menu) : menu] });
92
+ return _jsxs("div", { id: listboxId, ref: overlayRef, "data-select-overlay": true, className: cx.call(undefined, overlayStyle, popupClassName), role: "listbox", "aria-multiselectable": multiple || undefined, "aria-disabled": disabled || undefined, children: [_jsx("div", { "aria-hidden": "true", "data-select-option-measure": "", className: cx(optionStyle, measureStyle), children: "M" }), dropdownRender ? dropdownRender(menu) : menu] });
97
93
  };
98
94
  export default SelectOverlay;