@synerise/ds-select 2.0.4 → 2.1.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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,12 @@
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
+ # [2.1.0](https://github.com/synerise/synerise-design/compare/@synerise/ds-select@2.0.4...@synerise/ds-select@2.1.0) (2026-09-16)
7
+
8
+ ### Features
9
+
10
+ - **list-item:** add content-fitting size="auto" ([e269f0a](https://github.com/synerise/synerise-design/commit/e269f0a48ca7472f4766a50a957e22d7988c5e13))
11
+
6
12
  ## [2.0.4](https://github.com/synerise/synerise-design/compare/@synerise/ds-select@2.0.3...@synerise/ds-select@2.0.4) (2026-09-08)
7
13
 
8
14
  **Note:** Version bump only for package @synerise/ds-select
@@ -1,8 +1,9 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
- import { forwardRef, memo, useRef, useLayoutEffect, useCallback, useEffect, useMemo } from "react";
2
+ import { forwardRef, memo, useRef, useCallback, useMemo, useEffect } from "react";
3
3
  import { VariableSizeList } from "react-window";
4
4
  import Loader from "@synerise/ds-loader";
5
5
  import Scrollbar from "@synerise/ds-scrollbar";
6
+ import { useMeasuredRow, useMeasuredRowHeights } from "@synerise/ds-utils";
6
7
  import { VirtualRow, OptionItem, DropdownWrapper, Loading, NotFound, ScrollList, Inner } from "../Select.styles.js";
7
8
  import { cx, DEFAULT_LIST_ITEM_HEIGHT, MAX_MEASURED_ROWS, DEFAULT_LIST_HEIGHT, OVERSCAN_COUNT } from "../utils/helpers.js";
8
9
  const LIST_STYLE = {
@@ -26,20 +27,7 @@ const OptionRow = memo(({
26
27
  measureRow
27
28
  } = data;
28
29
  const option = options[index];
29
- const rowRef = useRef(null);
30
- useLayoutEffect(() => {
31
- const height = rowRef.current?.offsetHeight ?? 0;
32
- measureRow(index, height);
33
- });
34
- useLayoutEffect(() => {
35
- const node = rowRef.current;
36
- if (!node || typeof ResizeObserver === "undefined") {
37
- return void 0;
38
- }
39
- const observer = new ResizeObserver(() => measureRow(index, node.offsetHeight));
40
- observer.observe(node);
41
- return () => observer.disconnect();
42
- }, [index, measureRow]);
30
+ const rowRef = useMeasuredRow(index, measureRow);
43
31
  const isSelected = selectedValues.includes(option.value);
44
32
  const optionAttrs = Object.fromEntries(Object.entries(option).filter(([key]) => key.startsWith("data-") || key.startsWith("aria-")));
45
33
  return /* @__PURE__ */ jsx(
@@ -93,60 +81,32 @@ const OptionList = ({
93
81
  onOptionSelect,
94
82
  onPopupScroll
95
83
  }) => {
96
- const listRef = useRef(null);
97
84
  const scrollRef = useRef(null);
98
85
  const scrollOffsetRef = useRef(0);
99
- const sizeCacheRef = useRef(/* @__PURE__ */ new Map());
100
86
  const windowHeight = Number(listHeight) || DEFAULT_LIST_HEIGHT;
101
87
  const rowHeight = listItemHeight && listItemHeight > 0 ? listItemHeight : DEFAULT_LIST_ITEM_HEIGHT;
102
88
  const keyFor = useCallback((option) => rowKey ? rowKey(option) : option.key ?? option.value, [rowKey]);
103
- const getItemSize = useCallback((index) => {
104
- const option = options[index];
105
- if (!option) {
106
- return rowHeight;
107
- }
108
- return sizeCacheRef.current.get(keyFor(option)) ?? rowHeight;
109
- }, [options, keyFor, rowHeight]);
110
- const measureRow = useCallback((index, height) => {
111
- const option = options[index];
112
- if (!option || !height) {
113
- return;
114
- }
115
- const key = keyFor(option);
116
- if (sizeCacheRef.current.get(key) === height) {
117
- return;
118
- }
119
- sizeCacheRef.current.set(key, height);
120
- listRef.current?.resetAfterIndex(index);
121
- }, [options, keyFor]);
122
- const prevRowHeightRef = useRef(rowHeight);
123
- useLayoutEffect(() => {
124
- if (prevRowHeightRef.current === rowHeight) {
125
- return;
126
- }
127
- prevRowHeightRef.current = rowHeight;
128
- sizeCacheRef.current.clear();
129
- listRef.current?.resetAfterIndex(0);
130
- }, [rowHeight]);
131
- useEffect(() => {
132
- const cache = sizeCacheRef.current;
133
- if (cache.size > MAX_MEASURED_ROWS) {
134
- const live = new Set(options.map(keyFor));
135
- cache.forEach((_height, key) => {
136
- if (!live.has(key)) {
137
- cache.delete(key);
138
- }
139
- });
140
- }
141
- listRef.current?.resetAfterIndex(0, false);
142
- }, [options, keyFor]);
89
+ const rowKeys = useMemo(() => options.map(keyFor), [options, keyFor]);
90
+ const estimateRowHeight = useCallback(() => rowHeight, [rowHeight]);
91
+ const {
92
+ listRef,
93
+ getItemSize,
94
+ measureRow
95
+ } = useMeasuredRowHeights({
96
+ keys: rowKeys,
97
+ estimate: estimateRowHeight,
98
+ // `listItemHeight` can change under a stable option list; every cached
99
+ // measurement was taken against the old value, so they all have to go.
100
+ estimateVersion: rowHeight,
101
+ maxCachedRows: MAX_MEASURED_ROWS
102
+ });
143
103
  const scrollTo = useCallback((offset) => {
144
104
  scrollOffsetRef.current = offset;
145
105
  if (scrollRef.current) {
146
106
  scrollRef.current.scrollTop = offset;
147
107
  }
148
108
  listRef.current?.scrollTo(offset);
149
- }, []);
109
+ }, [listRef]);
150
110
  const isFirstQuery = useRef(true);
151
111
  useEffect(() => {
152
112
  if (isFirstQuery.current) {
@@ -183,7 +143,7 @@ const OptionList = ({
183
143
  scrollOffsetRef.current = scrollTop;
184
144
  listRef.current?.scrollTo(scrollTop);
185
145
  onPopupScroll?.(event);
186
- }, [onPopupScroll]);
146
+ }, [listRef, onPopupScroll]);
187
147
  const itemData = useMemo(() => ({
188
148
  options,
189
149
  selectedValues,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synerise/ds-select",
3
- "version": "2.0.4",
3
+ "version": "2.1.0",
4
4
  "description": "Select UI Component for the Synerise Design System",
5
5
  "license": "ISC",
6
6
  "repository": "synerise/synerise-design",
@@ -42,14 +42,14 @@
42
42
  ],
43
43
  "types": "dist/index.d.ts",
44
44
  "dependencies": {
45
- "@synerise/ds-dropdown": "^2.0.3",
46
- "@synerise/ds-form-field": "^2.0.2",
47
- "@synerise/ds-icon": "^2.1.0",
48
- "@synerise/ds-list-item": "^2.0.2",
45
+ "@synerise/ds-dropdown": "^2.1.0",
46
+ "@synerise/ds-form-field": "^2.0.3",
47
+ "@synerise/ds-icon": "^2.1.1",
48
+ "@synerise/ds-list-item": "^2.1.0",
49
49
  "@synerise/ds-loader": "^2.0.0",
50
- "@synerise/ds-scrollbar": "^2.0.1",
51
- "@synerise/ds-tooltip": "^2.0.2",
52
- "@synerise/ds-utils": "^2.0.0",
50
+ "@synerise/ds-scrollbar": "^2.0.2",
51
+ "@synerise/ds-tooltip": "^2.0.3",
52
+ "@synerise/ds-utils": "^2.1.0",
53
53
  "classnames": "^2.5.1",
54
54
  "react-window": "^1.8.11"
55
55
  },
@@ -59,5 +59,5 @@
59
59
  "styled-components": "^5.3.3",
60
60
  "vitest": "4"
61
61
  },
62
- "gitHead": "e0dced8fd3a1194eff2fafc1b7e385b29238af88"
62
+ "gitHead": "71f08fdaba8e7b80731e29a03883dc6a33f2b9db"
63
63
  }