@synerise/ds-select 1.4.0 → 1.5.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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,18 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [1.5.1](https://github.com/synerise/synerise-design/compare/@synerise/ds-select@1.5.0...@synerise/ds-select@1.5.1) (2026-08-13)
7
+
8
+ ### Bug Fixes
9
+
10
+ - honour Option children, optionLabelProp and optionFilterProp in select ([640a1c2](https://github.com/synerise/synerise-design/commit/640a1c27e677de165e195072a22bac891a065fac))
11
+
12
+ # [1.5.0](https://github.com/synerise/synerise-design/compare/@synerise/ds-select@1.4.0...@synerise/ds-select@1.5.0) (2026-08-11)
13
+
14
+ ### Features
15
+
16
+ - **select:** support maxTagCount="responsive" fit-to-width chips ([cb113cf](https://github.com/synerise/synerise-design/commit/cb113cf9f38a7ae112e55a8d159185f128fa2e4a))
17
+
6
18
  # [1.4.0](https://github.com/synerise/synerise-design/compare/@synerise/ds-select@1.3.34...@synerise/ds-select@1.4.0) (2026-07-24)
7
19
 
8
20
  ### Bug Fixes
package/CLAUDE.md CHANGED
@@ -18,6 +18,7 @@ src/
18
18
  modules.d.ts — imports @testing-library/jest-dom
19
19
  hooks/
20
20
  useSelectOptions.ts — resolve options (prop → children), client filtering, tags create-row
21
+ useResponsiveTagCount.ts — maxTagCount="responsive": fit-to-width chip count (ResizeObserver)
21
22
  components/
22
23
  OptionList.tsx — dropdown overlay: loading / empty / scrollable listbox of options
23
24
  SelectorContent.tsx — selector inner content: chips / selected label / placeholder + search input
@@ -86,9 +87,10 @@ Kept so antd-era consumers need no change: `searchValue`, `onClear`, `onClick`,
86
87
  `mode`/`options`/`showSearch`/`filterOption`/`allowClear`/`open`/`onChange`/`onSearch`/… surface.
87
88
  `SelectHandler` type and the `SelectStyles.Selector` styled export are re-exported for parity.
88
89
 
89
- `maxTagCount` (collapse extra chips into a `+N` overflow chip), `maxTagTextLength` (truncate chip
90
- labels) and `onPopupScroll` are fully implemented. `listItemHeight`, `dropdownAlign` and
91
- `defaultActiveFirstOption` are accepted for compatibility but have **no runtime effect**.
90
+ `maxTagCount` (`number | 'responsive'` — collapse extra chips into a `+N` overflow chip, or fit them
91
+ to the selector width on one line), `maxTagTextLength` (truncate chip labels) and `onPopupScroll` are
92
+ fully implemented. `listItemHeight`, `dropdownAlign` and `defaultActiveFirstOption` are accepted for
93
+ compatibility but have **no runtime effect**.
92
94
 
93
95
  ## Usage patterns
94
96
 
@@ -151,6 +153,12 @@ Class hooks are `ds-select-*` (`.ds-select`, `.ds-select-selection-item`, `.ds-s
151
153
  - **Option resolution** — `useSelectOptions` returns `resolvedOptions` (from `options` prop, else
152
154
  `getOptionsFromChildren(children)`) and `displayedOptions` (after client filtering and, in `tags`
153
155
  mode, a create-row prepended for the typed text). `filterOption={false}` disables local filtering.
156
+ - **Row vs selector display (antd parity)** — the dropdown row renders `option.children ?? label ??
157
+ value`, the selector renders the field named by `optionLabelProp` (`label`, `children`, `value`,
158
+ `title`, …) and falls back to `label`. So `<Option label={compact}>{rich}</Option>` shows `rich` in
159
+ the list and `compact` in the selector, while options-as-data (no children) uses `label` for both.
160
+ - **Filtering** — `defaultFilter` matches the field named by `optionFilterProp` (`title`, `children`,
161
+ `value`, …), else `label` then `children`; non-string fields (JSX nodes) fall back to the value.
154
162
  - **`Option.value` is optional** — falls back to the element's React `key` (antd parity); callbacks
155
163
  expose `option.key`.
156
164
  - **Keyboard nav is hand-rolled (not ds-dropdown's)** — Select is a *combobox*: focus stays on the
@@ -163,6 +171,13 @@ Class hooks are `ds-select-*` (`.ds-select`, `.ds-select-selection-item`, `.ds-s
163
171
  selecting an option / scrolling isn't treated as a blur (needed by `subtle-form`'s revert-on-blur).
164
172
  - **`readOnly` is implemented via `disabled`** — both flags are ORed into `isDisabled`; the visual
165
173
  distinction comes from the `$readOnly` transient prop on `Selector`.
174
+ - **`maxTagCount="responsive"`** — `useResponsiveTagCount` measures an off-flow ghost row
175
+ (`TagMeasureRow`, one hidden chip per value + a worst-case `+N` chip) rather than the visible
176
+ chips: a hidden chip would measure 0, and the ghost's width never depends on the count derived
177
+ from it, so the `ResizeObserver` (on the chip row *and* the ghosts, for font/label changes) can't
178
+ feed itself. The row switches to `nowrap` and reserves 30 px of caret room unless disabled/readOnly.
179
+ Unmeasurable layout (SSR, `display: none`, no `ResizeObserver` → `window.resize` fallback) degrades
180
+ to showing every chip; a lone oversized chip ellipsis-clips instead of collapsing into `+ 1`.
166
181
  - **Controlled/uncontrolled** — `value`/`open`/`searchValue` are controlled when defined, else backed
167
182
  by internal state; `onSearch` still fires when `searchValue` is controlled.
168
183
  - **antd-free; no LESS** — styling is entirely styled-components. The old antd-era LESS
package/dist/Select.d.ts CHANGED
@@ -42,7 +42,7 @@ declare const SelectInner: React.ForwardRefExoticComponent<{
42
42
  showArrow?: boolean;
43
43
  suffixIcon?: ReactNode;
44
44
  tabIndex?: number;
45
- maxTagCount?: number;
45
+ maxTagCount?: number | "responsive";
46
46
  maxTagTextLength?: number;
47
47
  maxTagPlaceholder?: ReactNode | ((omittedValues: Array<{
48
48
  value: RawValueType;
package/dist/Select.js CHANGED
@@ -174,10 +174,16 @@ const SelectInner = forwardRef((props, ref) => {
174
174
  const $size = size === "large" ? "large" : "default";
175
175
  const labelFor = (v) => {
176
176
  const option = findOption(resolvedOptions, v);
177
- if (option && optionLabelProp === "value") {
178
- return option.value;
177
+ if (!option) {
178
+ return v;
179
179
  }
180
- return option?.label ?? v;
180
+ if (optionLabelProp) {
181
+ const custom = option[optionLabelProp];
182
+ if (custom !== void 0) {
183
+ return custom;
184
+ }
185
+ }
186
+ return option.label ?? v;
181
187
  };
182
188
  const setOpen = (next) => {
183
189
  if (isDisabled) {
@@ -38,7 +38,10 @@ export declare const SearchInputEl: import('styled-components').StyledComponent<
38
38
  $overlay?: boolean;
39
39
  }, never>;
40
40
  /** Wraps chips + the search input for multiple/tags mode. */
41
- export declare const MultiValueArea: import('styled-components').StyledComponent<"div", any, {}, never>;
41
+ export declare const MultiValueArea: import('styled-components').StyledComponent<"div", any, {
42
+ $responsive?: boolean;
43
+ }, never>;
44
+ export declare const TagMeasureRow: import('styled-components').StyledComponent<"div", any, {}, never>;
42
45
  export declare const Chip: import('styled-components').StyledComponent<"span", any, {}, never>;
43
46
  export declare const ChipLabel: import('styled-components').StyledComponent<"span", any, {}, never>;
44
47
  export declare const ChipRemove: import('styled-components').StyledComponent<"span", any, {}, never>;
@@ -75,18 +75,22 @@ const SearchInputEl = /* @__PURE__ */ styled.input.withConfig({
75
75
  const MultiValueArea = /* @__PURE__ */ styled.div.withConfig({
76
76
  displayName: "Selectstyles__MultiValueArea",
77
77
  componentId: "sc-n9lk0v-16"
78
- })(["display:flex;flex-wrap:wrap;align-items:center;gap:4px;flex:1;min-width:0;"]);
78
+ })(["display:flex;flex-wrap:wrap;align-items:center;gap:4px;flex:1;min-width:0;", ""], (props) => props.$responsive && css(["position:relative;flex-wrap:nowrap;overflow:hidden;> .ds-select-selection-item,> .ds-select-selection-overflow{flex:0 0 auto;}> .ds-select-search{flex:1 1 0;min-width:0;}"]));
79
+ const TagMeasureRow = /* @__PURE__ */ styled.div.withConfig({
80
+ displayName: "Selectstyles__TagMeasureRow",
81
+ componentId: "sc-n9lk0v-17"
82
+ })(["position:absolute;top:0;left:0;width:100%;height:0;display:flex;flex-wrap:nowrap;gap:4px;overflow:hidden;visibility:hidden;pointer-events:none;> *{flex:0 0 auto;}"]);
79
83
  const Chip = /* @__PURE__ */ styled.span.withConfig({
80
84
  displayName: "Selectstyles__Chip",
81
- componentId: "sc-n9lk0v-17"
85
+ componentId: "sc-n9lk0v-18"
82
86
  })(["position:relative;display:inline-flex;align-items:center;box-sizing:border-box;width:max-content;max-width:100%;min-height:24px;padding:0 8px;background:", ";border:none;border-radius:3px;font-size:13px;line-height:1;color:", ";&:hover{background:", ";color:", ";}&:hover .ds-select-selection-item-label{max-width:calc(100% - 24px);}&:hover .ds-select-selection-item-remove{display:inline-flex;}"], (props) => props.theme.palette["grey-200"], (props) => props.theme.palette["grey-600"], (props) => props.theme.palette["grey-300"], (props) => props.theme.palette["grey-800"]);
83
87
  const ChipLabel = /* @__PURE__ */ styled.span.withConfig({
84
88
  displayName: "Selectstyles__ChipLabel",
85
- componentId: "sc-n9lk0v-18"
89
+ componentId: "sc-n9lk0v-19"
86
90
  })(["max-width:100%;min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;"]);
87
91
  const ChipRemove = /* @__PURE__ */ styled.span.withConfig({
88
92
  displayName: "Selectstyles__ChipRemove",
89
- componentId: "sc-n9lk0v-19"
93
+ componentId: "sc-n9lk0v-20"
90
94
  })(["position:absolute;right:4px;top:50%;transform:translateY(-50%);display:none;align-items:center;justify-content:center;width:24px;height:24px;cursor:pointer;color:", ";"], (props) => props.theme.palette["red-600"]);
91
95
  export {
92
96
  Arrow,
@@ -108,5 +112,6 @@ export {
108
112
  SelectWrapper,
109
113
  SelectionItem,
110
114
  Selector,
111
- SuffixWrapper
115
+ SuffixWrapper,
116
+ TagMeasureRow
112
117
  };
@@ -14,12 +14,12 @@ export type SelectOption = {
14
14
  value: RawValueType;
15
15
  /** antd-parity alias of `value`; some consumers read `option.key` in callbacks. */
16
16
  key?: RawValueType;
17
- /** Rendered content in the dropdown row (and the selector unless `optionLabelProp` picks another field). */
17
+ /** Selector display, and the dropdown row too unless `children` carries its own content. */
18
18
  label?: ReactNode;
19
19
  disabled?: boolean;
20
20
  /** Native `title` on the option (hover tooltip / accessible text), mirrors antd. */
21
21
  title?: ReactNode;
22
- /** antd parity: raw children of `<Select.Option>`; some consumers read `option.children`. */
22
+ /** Raw children of `<Select.Option>` the dropdown row content (antd parity). */
23
23
  children?: ReactNode;
24
24
  /** antd parity: forwarded to the rendered option row. */
25
25
  style?: CSSProperties;
@@ -85,8 +85,12 @@ export type SelectProps<VT extends SelectValue = SelectValue> = {
85
85
  suffixIcon?: ReactNode;
86
86
  /** antd parity: tab index forwarded to the selector / search input. */
87
87
  tabIndex?: number;
88
- /** Max chips rendered in `multiple` / `tags` mode before the rest collapse into an overflow chip. */
89
- maxTagCount?: number;
88
+ /**
89
+ * Max chips rendered in `multiple` / `tags` mode before the rest collapse into
90
+ * an overflow chip. `'responsive'` (antd parity) instead fits as many chips as
91
+ * the selector width allows on a single line and recomputes on resize.
92
+ */
93
+ maxTagCount?: number | 'responsive';
90
94
  /** Max characters shown per chip label; longer labels are truncated with an ellipsis. */
91
95
  maxTagTextLength?: number;
92
96
  /**
@@ -25,7 +25,25 @@ const OptionList = ({
25
25
  }, children: loading ? /* @__PURE__ */ jsx(Loading, { className: "ds-select-loading", children: /* @__PURE__ */ jsx(Loader, { size: "M" }) }) : options.length === 0 ? /* @__PURE__ */ jsx(NotFound, { className: "ds-select-empty", children: notFoundContent }) : /* @__PURE__ */ jsx(ScrollList, { children: /* @__PURE__ */ jsx(Scrollbar, { absolute: true, maxHeight: Number(listHeight) || DEFAULT_LIST_HEIGHT, onScroll: onPopupScroll ? (event) => onPopupScroll(event) : void 0, children: /* @__PURE__ */ jsx(Inner, { role: "listbox", id: listboxId, "aria-multiselectable": isMultiple || void 0, children: options.map((option, index) => {
26
26
  const isSelected = selectedValues.includes(option.value);
27
27
  const optionAttrs = Object.fromEntries(Object.entries(option).filter(([key]) => key.startsWith("data-") || key.startsWith("aria-")));
28
- return /* @__PURE__ */ jsx(OptionItem, { id: optionDomId(index), role: "option", className: cx("ds-select-item-option", isSelected && "ds-select-item-option-selected", index === activeIndex && "ds-select-item-option-active"), selected: isSelected, "aria-selected": isSelected, "data-testid": "select-option", title: typeof option.title === "string" ? option.title : void 0, text: option.label ?? option.value, style: option.style, disabled: option.disabled, onMouseEnter: () => onOptionActivate(index), onClick: () => onOptionSelect(option), ...optionAttrs }, rowKey ? rowKey(option) : option.value);
28
+ return /* @__PURE__ */ jsx(
29
+ OptionItem,
30
+ {
31
+ id: optionDomId(index),
32
+ role: "option",
33
+ className: cx("ds-select-item-option", isSelected && "ds-select-item-option-selected", index === activeIndex && "ds-select-item-option-active"),
34
+ selected: isSelected,
35
+ "aria-selected": isSelected,
36
+ "data-testid": "select-option",
37
+ title: typeof option.title === "string" ? option.title : void 0,
38
+ text: option.children ?? option.label ?? option.value,
39
+ style: option.style,
40
+ disabled: option.disabled,
41
+ onMouseEnter: () => onOptionActivate(index),
42
+ onClick: () => onOptionSelect(option),
43
+ ...optionAttrs
44
+ },
45
+ rowKey ? rowKey(option) : option.value
46
+ );
29
47
  }) }) }) }) });
30
48
  export {
31
49
  OptionList
@@ -10,7 +10,7 @@ type SelectorContentProps = {
10
10
  effectiveQuery: string;
11
11
  labelFor: (value: RawValueType) => ReactNode;
12
12
  onRemoveValue: (value: RawValueType) => void;
13
- maxTagCount?: number;
13
+ maxTagCount?: number | 'responsive';
14
14
  maxTagTextLength?: number;
15
15
  maxTagPlaceholder?: ReactNode | ((omittedValues: Array<{
16
16
  value: RawValueType;
@@ -1,6 +1,7 @@
1
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
2
  import Icon, { CloseS } from "@synerise/ds-icon";
3
- import { SearchInputEl, MultiValueArea, Chip, ChipLabel, ChipRemove, SelectionItem, Placeholder } from "../Select.styles.js";
3
+ import { SearchInputEl, MultiValueArea, TagMeasureRow, Chip, ChipLabel, ChipRemove, SelectionItem, Placeholder } from "../Select.styles.js";
4
+ import { useResponsiveTagCount } from "../hooks/useResponsiveTagCount.js";
4
5
  const SelectorContent = ({
5
6
  isMultiple,
6
7
  showSearch,
@@ -28,9 +29,28 @@ const SelectorContent = ({
28
29
  onSearchKeyDown
29
30
  }) => {
30
31
  const isSingleSearch = !!showSearch && !isMultiple;
32
+ const isResponsive = maxTagCount === "responsive";
33
+ const {
34
+ areaRef,
35
+ measureRef,
36
+ visibleCount
37
+ } = useResponsiveTagCount({
38
+ enabled: isResponsive && isMultiple,
39
+ items: selectedValues,
40
+ reserveInput: !isDisabled
41
+ });
31
42
  const searchInput = /* @__PURE__ */ jsx(SearchInputEl, { ref: inputRef, $overlay: isSingleSearch, className: "ds-select-search", value: effectiveQuery, onChange: onSearchChange, onKeyDown: onSearchKeyDown, placeholder: hasValue ? void 0 : placeholderStr, disabled: isDisabled, autoFocus, maxLength, autoComplete: "off", readOnly: !hasInput, id, role: "combobox", "aria-autocomplete": "list", "aria-expanded": isOpen, "aria-haspopup": "listbox", "aria-controls": isOpen ? listboxId : void 0, "aria-activedescendant": isOpen && activeIndex >= 0 ? optionDomId(activeIndex) : void 0 });
32
43
  if (isMultiple) {
33
- const displayedValues = typeof maxTagCount === "number" ? selectedValues.slice(0, Math.max(maxTagCount, 0)) : selectedValues;
44
+ const displayCount = (() => {
45
+ if (isResponsive) {
46
+ return visibleCount ?? selectedValues.length;
47
+ }
48
+ if (typeof maxTagCount === "number") {
49
+ return Math.max(maxTagCount, 0);
50
+ }
51
+ return selectedValues.length;
52
+ })();
53
+ const displayedValues = selectedValues.slice(0, displayCount);
34
54
  const omittedValues = selectedValues.slice(displayedValues.length);
35
55
  const chipLabel = (v) => {
36
56
  const label = labelFor(v);
@@ -39,11 +59,16 @@ const SelectorContent = ({
39
59
  }
40
60
  return label;
41
61
  };
42
- const overflowContent = typeof maxTagPlaceholder === "function" ? maxTagPlaceholder(omittedValues.map((v) => ({
62
+ const overflowContentFor = (omitted) => typeof maxTagPlaceholder === "function" ? maxTagPlaceholder(omitted.map((v) => ({
43
63
  value: v,
44
64
  label: labelFor(v)
45
- }))) : maxTagPlaceholder ?? `+ ${omittedValues.length}`;
46
- return /* @__PURE__ */ jsxs(MultiValueArea, { children: [
65
+ }))) : maxTagPlaceholder ?? `+ ${omitted.length}`;
66
+ const overflowContent = overflowContentFor(omittedValues);
67
+ return /* @__PURE__ */ jsxs(MultiValueArea, { ref: areaRef, className: "ds-select-selection-list", $responsive: isResponsive, children: [
68
+ isResponsive && /* @__PURE__ */ jsxs(TagMeasureRow, { ref: measureRef, "aria-hidden": true, children: [
69
+ selectedValues.map((v) => /* @__PURE__ */ jsx(Chip, { "data-measure-chip": "", children: /* @__PURE__ */ jsx(ChipLabel, { children: chipLabel(v) }) }, v)),
70
+ /* @__PURE__ */ jsx(Chip, { "data-measure-overflow": "", children: /* @__PURE__ */ jsx(ChipLabel, { children: overflowContentFor(selectedValues) }) })
71
+ ] }),
47
72
  displayedValues.map((v) => /* @__PURE__ */ jsxs(Chip, { className: "ds-select-selection-item", children: [
48
73
  /* @__PURE__ */ jsx(ChipLabel, { className: "ds-select-selection-item-label", children: chipLabel(v) }),
49
74
  /* @__PURE__ */ jsx(ChipRemove, { className: "ds-select-selection-item-remove", onMouseDown: (e) => {
@@ -0,0 +1,27 @@
1
+ import { RefObject } from 'react';
2
+ import { RawValueType } from '../Select.types';
3
+ type UseResponsiveTagCountParams = {
4
+ /** `maxTagCount === 'responsive'` in a multi-value mode. */
5
+ enabled: boolean;
6
+ /** Every selected value, in render order — one ghost chip each. */
7
+ items: RawValueType[];
8
+ /** Keep room for the search caret (skipped when disabled / readOnly). */
9
+ reserveInput: boolean;
10
+ };
11
+ type UseResponsiveTagCountResult = {
12
+ /** Attach to the chip row — its width is the space chips must fit into. */
13
+ areaRef: RefObject<HTMLDivElement>;
14
+ /** Attach to the off-flow row holding one ghost chip per value + the overflow chip. */
15
+ measureRef: RefObject<HTMLDivElement>;
16
+ /** Chips that fit on one line; `null` while unmeasured (SSR, hidden, zero-width) — render all. */
17
+ visibleCount: number | null;
18
+ };
19
+ /**
20
+ * Fit-to-width chip count for `maxTagCount="responsive"` (antd / rc-overflow parity).
21
+ *
22
+ * Widths come from an off-flow ghost row rather than the visible chips: hiding a
23
+ * chip would zero its width, and the ghost's own size never depends on the count
24
+ * we derive from it — so the ResizeObserver can't feed itself.
25
+ */
26
+ export declare const useResponsiveTagCount: ({ enabled, items, reserveInput, }: UseResponsiveTagCountParams) => UseResponsiveTagCountResult;
27
+ export {};
@@ -0,0 +1,72 @@
1
+ import { useRef, useState, useCallback, useEffect, useLayoutEffect } from "react";
2
+ const CHIP_GAP = 4;
3
+ const INPUT_RESERVE = 30;
4
+ const useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
5
+ const useResponsiveTagCount = ({
6
+ enabled,
7
+ items,
8
+ reserveInput
9
+ }) => {
10
+ const areaRef = useRef(null);
11
+ const measureRef = useRef(null);
12
+ const [visibleCount, setVisibleCount] = useState(null);
13
+ const itemCount = items.length;
14
+ const itemsKey = items.join("\0");
15
+ const measure = useCallback(() => {
16
+ const area = areaRef.current;
17
+ const row = measureRef.current;
18
+ if (!enabled || !area || !row) {
19
+ return;
20
+ }
21
+ const areaWidth = area.clientWidth;
22
+ if (areaWidth <= 0) {
23
+ setVisibleCount(null);
24
+ return;
25
+ }
26
+ const ghosts = Array.from(row.children);
27
+ const widths = ghosts.slice(0, itemCount).map((chip) => chip.offsetWidth);
28
+ const overflowWidth = ghosts[itemCount]?.offsetWidth ?? 0;
29
+ const available = areaWidth - (reserveInput ? INPUT_RESERVE : 0);
30
+ const totalWidth = widths.reduce((sum, width, index) => sum + width + (index > 0 ? CHIP_GAP : 0), 0);
31
+ if (totalWidth <= available) {
32
+ setVisibleCount(itemCount);
33
+ return;
34
+ }
35
+ let used = 0;
36
+ let count = 0;
37
+ for (let index = 0; index < widths.length; index += 1) {
38
+ const next = used + widths[index] + (count > 0 ? CHIP_GAP : 0);
39
+ if (next + CHIP_GAP + overflowWidth > available) {
40
+ break;
41
+ }
42
+ used = next;
43
+ count += 1;
44
+ }
45
+ setVisibleCount(count === 0 && itemCount === 1 ? 1 : count);
46
+ }, [enabled, itemCount, reserveInput]);
47
+ useIsomorphicLayoutEffect(() => {
48
+ if (!enabled) {
49
+ setVisibleCount(null);
50
+ return void 0;
51
+ }
52
+ measure();
53
+ if (typeof ResizeObserver === "undefined") {
54
+ window.addEventListener("resize", measure);
55
+ return () => window.removeEventListener("resize", measure);
56
+ }
57
+ const observer = new ResizeObserver(() => measure());
58
+ if (areaRef.current) {
59
+ observer.observe(areaRef.current);
60
+ }
61
+ Array.from(measureRef.current?.children ?? []).forEach((ghost) => observer.observe(ghost));
62
+ return () => observer.disconnect();
63
+ }, [enabled, measure, itemsKey]);
64
+ return {
65
+ areaRef,
66
+ measureRef,
67
+ visibleCount
68
+ };
69
+ };
70
+ export {
71
+ useResponsiveTagCount
72
+ };
@@ -5,5 +5,5 @@ export declare const DEFAULT_LIST_HEIGHT = 256;
5
5
  export declare const cx: (...classes: (string | false | undefined)[]) => string;
6
6
  /** Normalise a `SelectValue` to an array of raw values. */
7
7
  export declare const toArray: (value: SelectValue) => RawValueType[];
8
- /** Built-in filter: case-insensitive substring on `optionFilterProp` (or label/value). */
8
+ /** Built-in filter: case-insensitive substring on `optionFilterProp` (or label/children/value). */
9
9
  export declare const defaultFilter: (optionFilterProp?: string) => FilterOptionFn;
@@ -7,7 +7,8 @@ const toArray = (value) => {
7
7
  return Array.isArray(value) ? value : [value];
8
8
  };
9
9
  const defaultFilter = (optionFilterProp) => (input, option) => {
10
- const haystack = optionFilterProp === "value" ? String(option.value) : typeof option.label === "string" ? option.label : String(option.value);
10
+ const candidates = optionFilterProp ? [option[optionFilterProp]] : [option.label, option.children];
11
+ const haystack = candidates.find((candidate) => typeof candidate === "string") ?? String(option.value);
11
12
  return haystack.toLowerCase().includes(input.toLowerCase());
12
13
  };
13
14
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synerise/ds-select",
3
- "version": "1.4.0",
3
+ "version": "1.5.1",
4
4
  "description": "Select UI Component for the Synerise Design System",
5
5
  "license": "ISC",
6
6
  "repository": "synerise/synerise-design",
@@ -42,13 +42,13 @@
42
42
  ],
43
43
  "types": "dist/index.d.ts",
44
44
  "dependencies": {
45
- "@synerise/ds-dropdown": "^1.3.21",
46
- "@synerise/ds-form-field": "^1.3.24",
45
+ "@synerise/ds-dropdown": "^1.3.22",
46
+ "@synerise/ds-form-field": "^1.3.25",
47
47
  "@synerise/ds-icon": "^1.18.5",
48
- "@synerise/ds-list-item": "^1.6.2",
48
+ "@synerise/ds-list-item": "^1.6.3",
49
49
  "@synerise/ds-loader": "^1.0.18",
50
50
  "@synerise/ds-scrollbar": "^1.5.2",
51
- "@synerise/ds-tooltip": "^1.5.4",
51
+ "@synerise/ds-tooltip": "^1.5.5",
52
52
  "@synerise/ds-utils": "^1.10.2",
53
53
  "classnames": "^2.5.1"
54
54
  },
@@ -58,5 +58,5 @@
58
58
  "styled-components": "^5.3.3",
59
59
  "vitest": "4"
60
60
  },
61
- "gitHead": "018414e0d7ffd102a07f8aa35fa2e2d3d4ed19e2"
61
+ "gitHead": "0a103024461277b2f410e19a88d437764017c1d1"
62
62
  }