@coinbase/cds-web 8.25.1 → 8.26.0

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.
Files changed (34) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dts/alpha/select/DefaultSelectControl.d.ts +1 -1
  3. package/dts/alpha/select/DefaultSelectControl.d.ts.map +1 -1
  4. package/dts/alpha/select/DefaultSelectDropdown.d.ts.map +1 -1
  5. package/dts/alpha/select/DefaultSelectOptionGroup.d.ts +8 -0
  6. package/dts/alpha/select/DefaultSelectOptionGroup.d.ts.map +1 -0
  7. package/dts/alpha/select/Select.d.ts +21 -454
  8. package/dts/alpha/select/Select.d.ts.map +1 -1
  9. package/dts/alpha/select/index.d.ts +2 -0
  10. package/dts/alpha/select/index.d.ts.map +1 -1
  11. package/dts/alpha/select/types.d.ts +594 -0
  12. package/dts/alpha/select/types.d.ts.map +1 -0
  13. package/dts/alpha/select-chip/SelectChip.d.ts +26 -0
  14. package/dts/alpha/select-chip/SelectChip.d.ts.map +1 -0
  15. package/dts/alpha/select-chip/SelectChipControl.d.ts +13 -0
  16. package/dts/alpha/select-chip/SelectChipControl.d.ts.map +1 -0
  17. package/dts/alpha/select-chip/index.d.ts +3 -0
  18. package/dts/alpha/select-chip/index.d.ts.map +1 -0
  19. package/dts/chips/Chip.d.ts.map +1 -1
  20. package/dts/chips/SelectChip.d.ts +8 -0
  21. package/dts/chips/SelectChip.d.ts.map +1 -1
  22. package/dts/controls/SearchInput.d.ts +1 -1
  23. package/esm/alpha/select/DefaultSelectControl.js +46 -8
  24. package/esm/alpha/select/DefaultSelectDropdown.js +137 -60
  25. package/esm/alpha/select/DefaultSelectOptionGroup.js +118 -0
  26. package/esm/alpha/select/Select.js +16 -31
  27. package/esm/alpha/select/index.js +3 -1
  28. package/esm/alpha/select/types.js +46 -0
  29. package/esm/alpha/select-chip/SelectChip.js +50 -0
  30. package/esm/alpha/select-chip/SelectChipControl.js +116 -0
  31. package/esm/alpha/select-chip/index.js +2 -0
  32. package/esm/chips/Chip.js +5 -2
  33. package/esm/chips/SelectChip.js +10 -0
  34. package/package.json +2 -2
@@ -0,0 +1,3 @@
1
+ export * from './SelectChip';
2
+ export * from './SelectChipControl';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/alpha/select-chip/index.tsx"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"Chip.d.ts","sourceRoot":"","sources":["../../src/chips/Chip.tsx"],"names":[],"mappings":"AAkBA;;;;GAIG;AACH,eAAO,MAAM,IAAI,sMA4HhB,CAAC"}
1
+ {"version":3,"file":"Chip.d.ts","sourceRoot":"","sources":["../../src/chips/Chip.tsx"],"names":[],"mappings":"AAkBA;;;;GAIG;AACH,eAAO,MAAM,IAAI,sMAgIhB,CAAC"}
@@ -3,6 +3,10 @@ import type { SelectBaseProps } from '../controls/Select';
3
3
  import type { DropdownProps } from '../dropdown';
4
4
  import type { ChipProps } from './ChipProps';
5
5
  export declare const SELECT_CHIP_DEFAULT_TEST_ID = 'select-chip';
6
+ /**
7
+ * @deprecated This component is deprecated. Please use the new SelectChip alpha component instead.
8
+ * @see {@link @coinbase/cds-web/alpha/select-chip/SelectChip}
9
+ */
6
10
  export type SelectChipProps = {
7
11
  /** Indicates that the control is being used to manipulate data elsewhere */
8
12
  active?: boolean;
@@ -13,6 +17,10 @@ export type SelectChipProps = {
13
17
  } & Omit<ChipProps, 'inverted' | 'children' | 'onBlur' | 'noScaleOnPress' | 'content'> &
14
18
  Pick<SelectBaseProps, 'onChange' | 'valueLabel' | 'placeholder' | 'value'> &
15
19
  Omit<DropdownProps, 'onChange' | 'children'>;
20
+ /**
21
+ * @deprecated This component is deprecated. Please use the new SelectChip alpha component instead.
22
+ * @see {@link @coinbase/cds-web/alpha/select-chip/SelectChip}
23
+ */
16
24
  export declare const SelectChip: React.MemoExoticComponent<
17
25
  React.ForwardRefExoticComponent<Omit<SelectChipProps, 'ref'> & React.RefAttributes<unknown>>
18
26
  >;
@@ -1 +1 @@
1
- {"version":3,"file":"SelectChip.d.ts","sourceRoot":"","sources":["../../src/chips/SelectChip.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkD,MAAM,OAAO,CAAC;AAGvE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAE1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAIjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAG7C,eAAO,MAAM,2BAA2B,gBAAgB,CAAC;AAEzD,MAAM,MAAM,eAAe,GAAG;IAC5B,4EAA4E;IAC5E,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;OAEG;IACH,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B,GAAG,IAAI,CAAC,SAAS,EAAE,UAAU,GAAG,UAAU,GAAG,QAAQ,GAAG,gBAAgB,GAAG,SAAS,CAAC,GACpF,IAAI,CAAC,eAAe,EAAE,UAAU,GAAG,YAAY,GAAG,aAAa,GAAG,OAAO,CAAC,GAC1E,IAAI,CAAC,aAAa,EAAE,UAAU,GAAG,UAAU,CAAC,CAAC;AAE/C,eAAO,MAAM,UAAU,yHAoFtB,CAAC"}
1
+ {"version":3,"file":"SelectChip.d.ts","sourceRoot":"","sources":["../../src/chips/SelectChip.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkD,MAAM,OAAO,CAAC;AAGvE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAE1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAIjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAG7C,eAAO,MAAM,2BAA2B,gBAAgB,CAAC;AAEzD;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,4EAA4E;IAC5E,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;OAEG;IACH,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B,GAAG,IAAI,CAAC,SAAS,EAAE,UAAU,GAAG,UAAU,GAAG,QAAQ,GAAG,gBAAgB,GAAG,SAAS,CAAC,GACpF,IAAI,CAAC,eAAe,EAAE,UAAU,GAAG,YAAY,GAAG,aAAa,GAAG,OAAO,CAAC,GAC1E,IAAI,CAAC,aAAa,EAAE,UAAU,GAAG,UAAU,CAAC,CAAC;AAE/C;;;GAGG;AACH,eAAO,MAAM,UAAU,yHAoFtB,CAAC"}
@@ -86,8 +86,8 @@ export declare const SearchInput: React.MemoExoticComponent<
86
86
  | 'accessibilityLabelledBy'
87
87
  | 'accessibilityHint'
88
88
  | 'enableColorSurge'
89
- | 'testIDMap'
90
89
  | 'helperTextErrorIconAccessibilityLabel'
90
+ | 'testIDMap'
91
91
  > & {
92
92
  /**
93
93
  * Callback is fired when a user hits enter on the keyboard. Can obtain the query
@@ -17,9 +17,11 @@ import { AnimatedCaret } from '../../motion/AnimatedCaret';
17
17
  import { Pressable } from '../../system/Pressable';
18
18
  import { Text } from '../../typography/Text';
19
19
  import { findClosestNonDisabledNodeIndex } from '../../utils/findClosestNonDisabledNodeIndex';
20
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
20
+ import { isSelectOptionGroup } from './Select';
21
+
21
22
  // The height is smaller for the inside label variant since the label takes
22
23
  // up space above the input.
24
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
23
25
  const LABEL_VARIANT_INSIDE_HEIGHT = 32;
24
26
  const COMPACT_HEIGHT = 40;
25
27
  const DEFAULT_HEIGHT = 56;
@@ -62,6 +64,42 @@ const DefaultSelectControlComponent = /*#__PURE__*/memo(/*#__PURE__*/forwardRef(
62
64
  const shouldShowCompactLabel = compact && label;
63
65
  const isMultiSelect = type === 'multi';
64
66
  const hasValue = value !== null && !(Array.isArray(value) && value.length === 0);
67
+
68
+ // Map of options to their values
69
+ // If multiple options share the same value, the first occurrence wins (matches native HTML select behavior)
70
+ const optionsMap = useMemo(() => {
71
+ const map = new Map();
72
+ const isDev = process.env.NODE_ENV !== 'production';
73
+ options.forEach((option, optionIndex) => {
74
+ if (isSelectOptionGroup(option)) {
75
+ option.options.forEach((groupOption, groupOptionIndex) => {
76
+ if (groupOption.value !== null) {
77
+ const value = groupOption.value;
78
+ // Only set if not already present (first wins)
79
+ if (!map.has(value)) {
80
+ map.set(value, groupOption);
81
+ } else if (isDev) {
82
+ console.warn("[Select] Duplicate option value detected: \"".concat(value, "\". ") + "The first occurrence will be used for display. " + "Found duplicate in group \"".concat(option.label, "\" at index ").concat(groupOptionIndex, ". ") + "First occurrence was at option index ".concat(optionIndex, "."));
83
+ }
84
+ }
85
+ });
86
+ } else {
87
+ // It's a single option
88
+ const singleOption = option;
89
+ if (singleOption.value !== null) {
90
+ const value = singleOption.value;
91
+ if (!map.has(value)) {
92
+ map.set(value, singleOption);
93
+ } else if (isDev) {
94
+ var _ref2, _existingOption$label;
95
+ const existingOption = map.get(value);
96
+ console.warn("[Select] Duplicate option value detected: \"".concat(value, "\". ") + "The first occurrence will be used for display. " + "Found duplicate at option index ".concat(optionIndex, ". ") + "First occurrence label: \"".concat((_ref2 = (_existingOption$label = existingOption === null || existingOption === void 0 ? void 0 : existingOption.label) !== null && _existingOption$label !== void 0 ? _existingOption$label : existingOption === null || existingOption === void 0 ? void 0 : existingOption.value) !== null && _ref2 !== void 0 ? _ref2 : 'unknown', "\"."));
97
+ }
98
+ }
99
+ }
100
+ });
101
+ return map;
102
+ }, [options]);
65
103
  const controlPressableRef = useRef(null);
66
104
  const valueNodeContainerRef = useRef(null);
67
105
  const handleUnselectValue = useCallback((event, index) => {
@@ -110,15 +148,15 @@ const DefaultSelectControlComponent = /*#__PURE__*/memo(/*#__PURE__*/forwardRef(
110
148
  })
111
149
  }) : label, [label, labelVariant, disabled, setOpen, classNames === null || classNames === void 0 ? void 0 : classNames.controlLabelNode, styles === null || styles === void 0 ? void 0 : styles.controlLabelNode]);
112
150
  const valueNode = useMemo(() => {
113
- var _ref4, _ref5, _option$label2;
151
+ var _ref5, _ref6, _option$label2;
114
152
  if (hasValue && isMultiSelect) {
115
153
  const valuesToShow = value.length <= maxSelectedOptionsToShow ? value : value.slice(0, maxSelectedOptionsToShow);
116
- const optionsToShow = valuesToShow.map(value => options.find(option => option.value === value)).filter(Boolean);
154
+ const optionsToShow = valuesToShow.map(value => optionsMap.get(value)).filter(option => option !== undefined);
117
155
  return /*#__PURE__*/_jsxs(HStack, {
118
156
  flexWrap: "wrap",
119
157
  gap: 1,
120
158
  children: [optionsToShow.map((option, index) => {
121
- var _option$value, _ref2, _ref3, _option$label;
159
+ var _option$value, _ref3, _ref4, _option$label;
122
160
  const accessibilityLabel = typeof option.label === 'string' ? option.label : typeof option.description === 'string' ? option.description : (_option$value = option.value) !== null && _option$value !== void 0 ? _option$value : '';
123
161
  return /*#__PURE__*/_jsx(InputChip, {
124
162
  compact: true,
@@ -129,7 +167,7 @@ const DefaultSelectControlComponent = /*#__PURE__*/memo(/*#__PURE__*/forwardRef(
129
167
  invertColorScheme: false,
130
168
  maxWidth: 200,
131
169
  onClick: event => handleUnselectValue(event, index),
132
- children: (_ref2 = (_ref3 = (_option$label = option.label) !== null && _option$label !== void 0 ? _option$label : option.description) !== null && _ref3 !== void 0 ? _ref3 : option.value) !== null && _ref2 !== void 0 ? _ref2 : ''
170
+ children: (_ref3 = (_ref4 = (_option$label = option.label) !== null && _option$label !== void 0 ? _option$label : option.description) !== null && _ref4 !== void 0 ? _ref4 : option.value) !== null && _ref3 !== void 0 ? _ref3 : ''
133
171
  }, option.value);
134
172
  }), value.length - maxSelectedOptionsToShow > 0 && /*#__PURE__*/_jsx(InputChip, {
135
173
  compact: true,
@@ -140,8 +178,8 @@ const DefaultSelectControlComponent = /*#__PURE__*/memo(/*#__PURE__*/forwardRef(
140
178
  })]
141
179
  });
142
180
  }
143
- const option = options.find(option => option.value === value);
144
- const label = (_ref4 = (_ref5 = (_option$label2 = option === null || option === void 0 ? void 0 : option.label) !== null && _option$label2 !== void 0 ? _option$label2 : option === null || option === void 0 ? void 0 : option.description) !== null && _ref5 !== void 0 ? _ref5 : option === null || option === void 0 ? void 0 : option.value) !== null && _ref4 !== void 0 ? _ref4 : placeholder;
181
+ const option = !isMultiSelect ? optionsMap.get(value) : undefined;
182
+ const label = (_ref5 = (_ref6 = (_option$label2 = option === null || option === void 0 ? void 0 : option.label) !== null && _option$label2 !== void 0 ? _option$label2 : option === null || option === void 0 ? void 0 : option.description) !== null && _ref6 !== void 0 ? _ref6 : option === null || option === void 0 ? void 0 : option.value) !== null && _ref5 !== void 0 ? _ref5 : placeholder;
145
183
  const content = hasValue ? label : placeholder;
146
184
  return typeof content === 'string' ? /*#__PURE__*/_jsx(Text, {
147
185
  as: "p",
@@ -151,7 +189,7 @@ const DefaultSelectControlComponent = /*#__PURE__*/memo(/*#__PURE__*/forwardRef(
151
189
  overflow: "truncate",
152
190
  children: content
153
191
  }) : content;
154
- }, [hasValue, isMultiSelect, options, placeholder, value, maxSelectedOptionsToShow, hiddenSelectedOptionsLabel, removeSelectedOptionAccessibilityLabel, handleUnselectValue]);
192
+ }, [hasValue, isMultiSelect, optionsMap, placeholder, value, maxSelectedOptionsToShow, hiddenSelectedOptionsLabel, removeSelectedOptionAccessibilityLabel, handleUnselectValue]);
155
193
  const inputNode = useMemo(() =>
156
194
  /*#__PURE__*/
157
195
  // We don't offer control over setting the role since this must always be a button
@@ -1,5 +1,5 @@
1
- const _excluded = ["type", "options", "value", "onChange", "open", "setOpen", "controlRef", "disabled", "style", "styles", "className", "classNames", "compact", "label", "end", "selectAllLabel", "emptyOptionsLabel", "clearAllLabel", "hideSelectAll", "accessory", "media", "SelectOptionComponent", "SelectAllOptionComponent", "SelectEmptyDropdownContentsComponent", "accessibilityLabel", "accessibilityRoles"],
2
- _excluded2 = ["Component", "media", "accessory", "end"];
1
+ const _excluded = ["type", "options", "value", "onChange", "open", "setOpen", "controlRef", "disabled", "style", "styles", "className", "classNames", "compact", "label", "end", "selectAllLabel", "emptyOptionsLabel", "clearAllLabel", "hideSelectAll", "accessory", "media", "SelectOptionComponent", "SelectAllOptionComponent", "SelectEmptyDropdownContentsComponent", "SelectOptionGroupComponent", "accessibilityLabel", "accessibilityRoles"],
2
+ _excluded2 = ["Component", "media", "accessory", "end", "disabled"];
3
3
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
4
4
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
5
5
  function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
@@ -19,7 +19,8 @@ import { FocusTrap } from '../../overlays/FocusTrap';
19
19
  import { DefaultSelectAllOption } from './DefaultSelectAllOption';
20
20
  import { DefaultSelectEmptyDropdownContents } from './DefaultSelectEmptyDropdownContents';
21
21
  import { DefaultSelectOption } from './DefaultSelectOption';
22
- import { defaultAccessibilityRoles } from './Select';
22
+ import { DefaultSelectOptionGroup } from './DefaultSelectOptionGroup';
23
+ import { defaultAccessibilityRoles, isSelectOptionGroup } from './Select';
23
24
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
24
25
  const initialStyle = {
25
26
  opacity: 0,
@@ -55,6 +56,7 @@ const DefaultSelectDropdownComponent = /*#__PURE__*/memo(/*#__PURE__*/forwardRef
55
56
  SelectOptionComponent = DefaultSelectOption,
56
57
  SelectAllOptionComponent = DefaultSelectAllOption,
57
58
  SelectEmptyDropdownContentsComponent = DefaultSelectEmptyDropdownContents,
59
+ SelectOptionGroupComponent = DefaultSelectOptionGroup,
58
60
  accessibilityLabel = 'Select dropdown',
59
61
  accessibilityRoles = defaultAccessibilityRoles
60
62
  } = _ref,
@@ -89,16 +91,71 @@ const DefaultSelectDropdownComponent = /*#__PURE__*/memo(/*#__PURE__*/forwardRef
89
91
  emptyContentsContainer: classNames === null || classNames === void 0 ? void 0 : classNames.emptyContentsContainer,
90
92
  emptyContentsText: classNames === null || classNames === void 0 ? void 0 : classNames.emptyContentsText
91
93
  }), [classNames === null || classNames === void 0 ? void 0 : classNames.emptyContentsContainer, classNames === null || classNames === void 0 ? void 0 : classNames.emptyContentsText]);
94
+ const optionGroupStyles = useMemo(() => ({
95
+ optionGroup: styles === null || styles === void 0 ? void 0 : styles.optionGroup,
96
+ option: styles === null || styles === void 0 ? void 0 : styles.option,
97
+ optionBlendStyles: styles === null || styles === void 0 ? void 0 : styles.optionBlendStyles,
98
+ optionCell: styles === null || styles === void 0 ? void 0 : styles.optionCell,
99
+ optionContent: styles === null || styles === void 0 ? void 0 : styles.optionContent,
100
+ optionLabel: styles === null || styles === void 0 ? void 0 : styles.optionLabel,
101
+ optionDescription: styles === null || styles === void 0 ? void 0 : styles.optionDescription,
102
+ selectAllDivider: styles === null || styles === void 0 ? void 0 : styles.selectAllDivider
103
+ }), [styles === null || styles === void 0 ? void 0 : styles.optionGroup, styles === null || styles === void 0 ? void 0 : styles.option, styles === null || styles === void 0 ? void 0 : styles.optionBlendStyles, styles === null || styles === void 0 ? void 0 : styles.optionCell, styles === null || styles === void 0 ? void 0 : styles.optionContent, styles === null || styles === void 0 ? void 0 : styles.optionLabel, styles === null || styles === void 0 ? void 0 : styles.optionDescription, styles === null || styles === void 0 ? void 0 : styles.selectAllDivider]);
104
+ const optionGroupClassNames = useMemo(() => ({
105
+ optionGroup: classNames === null || classNames === void 0 ? void 0 : classNames.optionGroup,
106
+ option: classNames === null || classNames === void 0 ? void 0 : classNames.option,
107
+ optionCell: classNames === null || classNames === void 0 ? void 0 : classNames.optionCell,
108
+ optionContent: classNames === null || classNames === void 0 ? void 0 : classNames.optionContent,
109
+ optionLabel: classNames === null || classNames === void 0 ? void 0 : classNames.optionLabel,
110
+ optionDescription: classNames === null || classNames === void 0 ? void 0 : classNames.optionDescription,
111
+ selectAllDivider: classNames === null || classNames === void 0 ? void 0 : classNames.selectAllDivider
112
+ }), [classNames === null || classNames === void 0 ? void 0 : classNames.optionGroup, classNames === null || classNames === void 0 ? void 0 : classNames.option, classNames === null || classNames === void 0 ? void 0 : classNames.optionCell, classNames === null || classNames === void 0 ? void 0 : classNames.optionContent, classNames === null || classNames === void 0 ? void 0 : classNames.optionLabel, classNames === null || classNames === void 0 ? void 0 : classNames.optionDescription, classNames === null || classNames === void 0 ? void 0 : classNames.selectAllDivider]);
113
+
114
+ // Flatten options to handle nested groups for select all logic
115
+ // Only include non-disabled options (exclude options from disabled groups and individually disabled options)
116
+ const flatOptionsForSelectAll = useMemo(() => {
117
+ // If the entire dropdown is disabled, no options should be selectable
118
+ if (disabled) return [];
119
+ const result = [];
120
+ options.forEach(option => {
121
+ if (isSelectOptionGroup(option)) {
122
+ var _option$disabled;
123
+ // It's a group, check if the group is disabled
124
+ const isGroupDisabled = (_option$disabled = option.disabled) !== null && _option$disabled !== void 0 ? _option$disabled : false;
125
+ if (!isGroupDisabled) {
126
+ // Only add options from non-disabled groups, and filter out individually disabled options
127
+ result.push(...option.options.filter(groupOption => !groupOption.disabled));
128
+ }
129
+ } else {
130
+ // It's a single option, only add if not disabled
131
+ if (!option.disabled) {
132
+ result.push(option);
133
+ }
134
+ }
135
+ });
136
+ return result;
137
+ }, [options, disabled]);
92
138
  const isMultiSelect = type === 'multi';
93
139
  const isSomeOptionsSelected = isMultiSelect ? value.length > 0 : false;
94
- const isAllOptionsSelected = isMultiSelect ? value.length === options.filter(o => o.value !== null).length : false;
140
+ // Only count non-disabled options when determining if all are selected
141
+ const enabledOptionsCount = flatOptionsForSelectAll.filter(o => o.value !== null).length;
142
+ const isAllOptionsSelected = isMultiSelect ? enabledOptionsCount > 0 && value.length === enabledOptionsCount : false;
95
143
  const toggleSelectAll = useCallback(() => {
96
- if (isAllOptionsSelected) onChange(null);else onChange(options.map(o => o.value).filter(o => o !== null && !(value !== null && value !== void 0 && value.includes(o))));
97
- }, [isAllOptionsSelected, onChange, options, value]);
144
+ if (isAllOptionsSelected) onChange(null);else onChange(flatOptionsForSelectAll.map(_ref2 => {
145
+ let {
146
+ value
147
+ } = _ref2;
148
+ return value;
149
+ }).filter(optionValue => optionValue !== null && !(value !== null && value !== void 0 && value.includes(optionValue))));
150
+ }, [isAllOptionsSelected, onChange, flatOptionsForSelectAll, value]);
98
151
  const handleClearAll = useCallback(event => {
99
152
  event.stopPropagation();
100
153
  onChange(null);
101
154
  }, [onChange]);
155
+ const handleOptionClick = useCallback(newValue => {
156
+ onChange(newValue);
157
+ if (!isMultiSelect) setOpen(false);
158
+ }, [onChange, isMultiSelect, setOpen]);
102
159
  const handleEscPress = useCallback(() => setOpen(false), [setOpen]);
103
160
  useEffect(() => {
104
161
  if (!controlRef.current) return;
@@ -109,43 +166,8 @@ const DefaultSelectDropdownComponent = /*#__PURE__*/memo(/*#__PURE__*/forwardRef
109
166
  return () => resizeObserver.disconnect();
110
167
  }, [controlRef]);
111
168
  const indeterminate = !isAllOptionsSelected && isSomeOptionsSelected ? true : false;
112
- const SelectAllOption = useMemo(() => /*#__PURE__*/_jsx(SelectAllOptionComponent, {
113
- accessibilityRole: accessibilityRoles === null || accessibilityRoles === void 0 ? void 0 : accessibilityRoles.option,
114
- accessory: accessory,
115
- blendStyles: styles === null || styles === void 0 ? void 0 : styles.optionBlendStyles,
116
- className: classNames === null || classNames === void 0 ? void 0 : classNames.option,
117
- classNames: optionClassNames,
118
- compact: compact,
119
- disabled: disabled,
120
- end: end !== null && end !== void 0 ? end : /*#__PURE__*/_jsx(Button, {
121
- compact: true,
122
- transparent: true,
123
- onClick: handleClearAll,
124
- role: "option",
125
- style: {
126
- margin: 'var(--space-0_5)'
127
- },
128
- width: "fit-content",
129
- children: clearAllLabel
130
- }),
131
- indeterminate: indeterminate,
132
- label: "".concat(selectAllLabel, " (").concat(options.filter(o => o.value !== null).length, ")"),
133
- media: media !== null && media !== void 0 ? media : /*#__PURE__*/_jsx(Checkbox, {
134
- readOnly: true,
135
- checked: isAllOptionsSelected,
136
- iconStyle: {
137
- opacity: 1
138
- },
139
- indeterminate: indeterminate,
140
- tabIndex: -1
141
- }),
142
- onClick: toggleSelectAll,
143
- selected: isAllOptionsSelected || isSomeOptionsSelected,
144
- style: styles === null || styles === void 0 ? void 0 : styles.option,
145
- styles: optionStyles,
146
- type: type,
147
- value: 'select-all'
148
- }, "select-all"), [SelectAllOptionComponent, accessibilityRoles === null || accessibilityRoles === void 0 ? void 0 : accessibilityRoles.option, accessory, styles === null || styles === void 0 ? void 0 : styles.optionBlendStyles, styles === null || styles === void 0 ? void 0 : styles.option, classNames === null || classNames === void 0 ? void 0 : classNames.option, optionClassNames, compact, disabled, end, handleClearAll, clearAllLabel, indeterminate, selectAllLabel, options, media, isAllOptionsSelected, toggleSelectAll, isSomeOptionsSelected, optionStyles, type]);
169
+ const shouldShowSelectAll = !hideSelectAll && isMultiSelect && options.length > 0;
170
+ const hasOptions = options.length > 0;
149
171
  return /*#__PURE__*/_jsx(AnimatePresence, {
150
172
  children: open && /*#__PURE__*/_jsx(Box, _objectSpread(_objectSpread({
151
173
  ref: ref,
@@ -174,17 +196,76 @@ const DefaultSelectDropdownComponent = /*#__PURE__*/memo(/*#__PURE__*/forwardRef
174
196
  flexDirection: "column",
175
197
  maxHeight: 252,
176
198
  overflow: "auto",
177
- children: [!hideSelectAll && isMultiSelect && options.length > 0 && SelectAllOption, options.length > 0 ? options.map(_ref2 => {
199
+ children: [shouldShowSelectAll && /*#__PURE__*/_jsx(SelectAllOptionComponent, {
200
+ accessibilityRole: accessibilityRoles === null || accessibilityRoles === void 0 ? void 0 : accessibilityRoles.option,
201
+ accessory: accessory,
202
+ className: classNames === null || classNames === void 0 ? void 0 : classNames.option,
203
+ classNames: optionClassNames,
204
+ compact: compact,
205
+ disabled: disabled,
206
+ end: end !== null && end !== void 0 ? end : /*#__PURE__*/_jsx(Button, {
207
+ compact: true,
208
+ transparent: true,
209
+ onClick: handleClearAll,
210
+ role: "option",
211
+ style: {
212
+ margin: 'var(--space-0_5)'
213
+ },
214
+ width: "fit-content",
215
+ children: clearAllLabel
216
+ }),
217
+ indeterminate: indeterminate,
218
+ label: "".concat(selectAllLabel, " (").concat(flatOptionsForSelectAll.filter(o => o.value !== null).length, ")"),
219
+ media: media !== null && media !== void 0 ? media : /*#__PURE__*/_jsx(Checkbox, {
220
+ readOnly: true,
221
+ checked: isAllOptionsSelected,
222
+ iconStyle: {
223
+ opacity: 1
224
+ },
225
+ indeterminate: indeterminate,
226
+ tabIndex: -1
227
+ }),
228
+ onClick: toggleSelectAll,
229
+ selected: isAllOptionsSelected || isSomeOptionsSelected,
230
+ style: styles === null || styles === void 0 ? void 0 : styles.option,
231
+ styles: optionStyles,
232
+ type: type,
233
+ value: 'select-all'
234
+ }, "select-all"), options.map(optionOrGroup => {
178
235
  var _ref3;
179
- let {
180
- Component,
236
+ // Check if it's a group (has 'options' property and 'label')
237
+ if (isSelectOptionGroup(optionOrGroup)) {
238
+ var _group$disabled;
239
+ const group = optionOrGroup;
240
+ return /*#__PURE__*/_jsx(SelectOptionGroupComponent, {
241
+ SelectOptionComponent: SelectOptionComponent,
242
+ accessibilityRole: accessibilityRoles === null || accessibilityRoles === void 0 ? void 0 : accessibilityRoles.option,
243
+ accessory: accessory,
244
+ classNames: optionGroupClassNames,
245
+ compact: compact,
246
+ disabled: (_group$disabled = group.disabled) !== null && _group$disabled !== void 0 ? _group$disabled : disabled,
247
+ end: end,
248
+ label: group.label,
249
+ media: media,
250
+ onChange: onChange,
251
+ options: group.options,
252
+ setOpen: setOpen,
253
+ styles: optionGroupStyles,
254
+ type: type,
255
+ value: value
256
+ }, "group-".concat(group.label));
257
+ }
258
+ const option = optionOrGroup;
259
+ const {
260
+ Component: optionComponent,
181
261
  media: optionMedia,
182
262
  accessory: optionAccessory,
183
- end: optionEnd
184
- } = _ref2,
185
- option = _objectWithoutProperties(_ref2, _excluded2);
186
- const RenderedSelectOption = Component !== null && Component !== void 0 ? Component : SelectOptionComponent;
187
- const selected = option.value !== null && isMultiSelect ? value.includes(option.value) : value === option.value;
263
+ end: optionEnd,
264
+ disabled: optionDisabled
265
+ } = option,
266
+ optionProps = _objectWithoutProperties(option, _excluded2);
267
+ const RenderedComponent = optionComponent !== null && optionComponent !== void 0 ? optionComponent : SelectOptionComponent;
268
+ const selected = optionProps.value !== null && isMultiSelect ? value.includes(optionProps.value) : value === optionProps.value;
188
269
  const defaultMedia = isMultiSelect ? /*#__PURE__*/_jsx(Checkbox, {
189
270
  "aria-hidden": true,
190
271
  readOnly: true,
@@ -202,26 +283,22 @@ const DefaultSelectDropdownComponent = /*#__PURE__*/memo(/*#__PURE__*/forwardRef
202
283
  },
203
284
  tabIndex: -1
204
285
  });
205
- return /*#__PURE__*/_jsx(RenderedSelectOption, _objectSpread({
286
+ return /*#__PURE__*/_jsx(RenderedComponent, _objectSpread({
206
287
  accessibilityRole: accessibilityRoles === null || accessibilityRoles === void 0 ? void 0 : accessibilityRoles.option,
207
288
  accessory: optionAccessory !== null && optionAccessory !== void 0 ? optionAccessory : accessory,
208
- blendStyles: styles === null || styles === void 0 ? void 0 : styles.optionBlendStyles,
209
289
  className: classNames === null || classNames === void 0 ? void 0 : classNames.option,
210
290
  classNames: optionClassNames,
211
291
  compact: compact,
212
- disabled: option.disabled || disabled,
292
+ disabled: optionDisabled || disabled,
213
293
  end: optionEnd !== null && optionEnd !== void 0 ? optionEnd : end,
214
294
  media: (_ref3 = optionMedia !== null && optionMedia !== void 0 ? optionMedia : media) !== null && _ref3 !== void 0 ? _ref3 : defaultMedia,
215
- onClick: newValue => {
216
- onChange(newValue);
217
- if (!isMultiSelect) setOpen(false);
218
- },
295
+ onClick: handleOptionClick,
219
296
  selected: selected,
220
297
  style: styles === null || styles === void 0 ? void 0 : styles.option,
221
298
  styles: optionStyles,
222
299
  type: type
223
- }, option), option.value);
224
- }) : /*#__PURE__*/_jsx(SelectEmptyDropdownContentsComponent, {
300
+ }, optionProps), optionProps.value);
301
+ }), !hasOptions && /*#__PURE__*/_jsx(SelectEmptyDropdownContentsComponent, {
225
302
  classNames: emptyDropdownContentsClassNames,
226
303
  label: emptyOptionsLabel,
227
304
  styles: emptyDropdownContentsStyles
@@ -0,0 +1,118 @@
1
+ const _excluded = ["Component", "media", "accessory", "end", "disabled"];
2
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
3
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
4
+ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
5
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
6
+ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
7
+ function _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var n = Object.getOwnPropertySymbols(e); for (r = 0; r < n.length; r++) o = n[r], -1 === t.indexOf(o) && {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; }
8
+ function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
9
+ import { memo, useCallback, useId, useMemo } from 'react';
10
+ import { Checkbox } from '../../controls/Checkbox';
11
+ import { Radio } from '../../controls/Radio';
12
+ import { VStack } from '../../layout';
13
+ import { Text } from '../../typography/Text';
14
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
15
+ const DefaultSelectOptionGroupComponent = /*#__PURE__*/memo(_ref => {
16
+ let {
17
+ label,
18
+ options,
19
+ SelectOptionComponent,
20
+ value,
21
+ onChange,
22
+ setOpen,
23
+ type,
24
+ accessibilityRole,
25
+ accessory,
26
+ media,
27
+ end,
28
+ disabled,
29
+ compact,
30
+ styles,
31
+ classNames
32
+ } = _ref;
33
+ const labelId = useId();
34
+ const optionStyles = useMemo(() => ({
35
+ optionCell: styles === null || styles === void 0 ? void 0 : styles.optionCell,
36
+ optionContent: styles === null || styles === void 0 ? void 0 : styles.optionContent,
37
+ optionLabel: styles === null || styles === void 0 ? void 0 : styles.optionLabel,
38
+ optionDescription: styles === null || styles === void 0 ? void 0 : styles.optionDescription,
39
+ selectAllDivider: styles === null || styles === void 0 ? void 0 : styles.selectAllDivider
40
+ }), [styles === null || styles === void 0 ? void 0 : styles.optionCell, styles === null || styles === void 0 ? void 0 : styles.optionContent, styles === null || styles === void 0 ? void 0 : styles.optionLabel, styles === null || styles === void 0 ? void 0 : styles.optionDescription, styles === null || styles === void 0 ? void 0 : styles.selectAllDivider]);
41
+ const optionClassNames = useMemo(() => ({
42
+ optionCell: classNames === null || classNames === void 0 ? void 0 : classNames.optionCell,
43
+ optionContent: classNames === null || classNames === void 0 ? void 0 : classNames.optionContent,
44
+ optionLabel: classNames === null || classNames === void 0 ? void 0 : classNames.optionLabel,
45
+ optionDescription: classNames === null || classNames === void 0 ? void 0 : classNames.optionDescription,
46
+ selectAllDivider: classNames === null || classNames === void 0 ? void 0 : classNames.selectAllDivider
47
+ }), [classNames === null || classNames === void 0 ? void 0 : classNames.optionCell, classNames === null || classNames === void 0 ? void 0 : classNames.optionContent, classNames === null || classNames === void 0 ? void 0 : classNames.optionLabel, classNames === null || classNames === void 0 ? void 0 : classNames.optionDescription, classNames === null || classNames === void 0 ? void 0 : classNames.selectAllDivider]);
48
+ const isMultiSelect = type === 'multi';
49
+ const handleOptionClick = useCallback(newValue => {
50
+ onChange(newValue);
51
+ if (!isMultiSelect) setOpen(false);
52
+ }, [onChange, isMultiSelect, setOpen]);
53
+ if (options.length === 0) {
54
+ return null;
55
+ }
56
+ return /*#__PURE__*/_jsxs(VStack, {
57
+ "aria-disabled": disabled,
58
+ "aria-labelledby": labelId,
59
+ background: "bg",
60
+ className: classNames === null || classNames === void 0 ? void 0 : classNames.optionGroup,
61
+ role: "group",
62
+ style: styles === null || styles === void 0 ? void 0 : styles.optionGroup,
63
+ children: [/*#__PURE__*/_jsx(Text, {
64
+ color: "fgMuted",
65
+ font: "caption",
66
+ id: labelId,
67
+ paddingX: 2,
68
+ paddingY: 2,
69
+ children: label
70
+ }), options.map(option => {
71
+ var _ref2;
72
+ const {
73
+ Component: optionComponent,
74
+ media: optionMedia,
75
+ accessory: optionAccessory,
76
+ end: optionEnd,
77
+ disabled: optionDisabled
78
+ } = option,
79
+ optionProps = _objectWithoutProperties(option, _excluded);
80
+ const RenderedComponent = optionComponent !== null && optionComponent !== void 0 ? optionComponent : SelectOptionComponent;
81
+ const selected = optionProps.value !== null && isMultiSelect ? value.includes(optionProps.value) : value === optionProps.value;
82
+ const defaultMedia = isMultiSelect ? /*#__PURE__*/_jsx(Checkbox, {
83
+ "aria-hidden": true,
84
+ readOnly: true,
85
+ checked: selected,
86
+ iconStyle: {
87
+ opacity: 1
88
+ },
89
+ tabIndex: -1
90
+ }) : /*#__PURE__*/_jsx(Radio, {
91
+ "aria-hidden": true,
92
+ readOnly: true,
93
+ checked: selected,
94
+ iconStyle: {
95
+ opacity: 1
96
+ },
97
+ tabIndex: -1
98
+ });
99
+ return /*#__PURE__*/_jsx(RenderedComponent, _objectSpread({
100
+ accessibilityRole: accessibilityRole,
101
+ accessory: optionAccessory !== null && optionAccessory !== void 0 ? optionAccessory : accessory,
102
+ className: classNames === null || classNames === void 0 ? void 0 : classNames.option,
103
+ classNames: optionClassNames,
104
+ compact: compact,
105
+ disabled: optionDisabled || disabled,
106
+ end: optionEnd !== null && optionEnd !== void 0 ? optionEnd : end,
107
+ media: (_ref2 = optionMedia !== null && optionMedia !== void 0 ? optionMedia : media) !== null && _ref2 !== void 0 ? _ref2 : defaultMedia,
108
+ onClick: handleOptionClick,
109
+ selected: selected,
110
+ style: styles === null || styles === void 0 ? void 0 : styles.option,
111
+ styles: optionStyles,
112
+ type: type
113
+ }, optionProps), optionProps.value);
114
+ })]
115
+ });
116
+ });
117
+ DefaultSelectOptionGroupComponent.displayName = 'DefaultSelectOptionGroup';
118
+ export const DefaultSelectOptionGroup = DefaultSelectOptionGroupComponent;