@synerise/ds-search 1.5.35 → 1.5.36

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
+ ## [1.5.36](https://github.com/Synerise/synerise-design/compare/@synerise/ds-search@1.5.35...@synerise/ds-search@1.5.36) (2026-08-13)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **search:** render expanded when mounted with a value ([6d0fe1d](https://github.com/Synerise/synerise-design/commit/6d0fe1d3673934f1044a152faeab292fcb9501b3))
11
+
6
12
  ## [1.5.35](https://github.com/Synerise/synerise-design/compare/@synerise/ds-search@1.5.34...@synerise/ds-search@1.5.35) (2026-08-11)
7
13
 
8
14
  **Note:** Version bump only for package @synerise/ds-search
package/CLAUDE.md CHANGED
@@ -11,10 +11,7 @@ src/
11
11
  Search.styles.tsx — styled-components for wrapper, dropdown, input, filter label, clear button
12
12
  const.ts — numeric constants (offsets, animation durations)
13
13
  index.ts — public exports
14
- modules.d.ts —less module declaration
15
- style/
16
- index.less — component base styles
17
- input.mixin.less — input mixin
14
+ modules.d.ts — testing-library/jest-dom ambient import
18
15
  Elements/
19
16
  index.ts — re-exports sub-components
20
17
  SearchInput/
@@ -247,7 +244,7 @@ import { SearchInput } from '@synerise/ds-search';
247
244
  ## Styling
248
245
 
249
246
  - `Search.styles.tsx` contains all styled-components; no CSS Modules.
250
- - `SearchWrapper` receives `$width` (px) and `inputOpen` to toggle `width: 32px` vs full width.
247
+ - `SearchWrapper` receives `$width` (px) and `inputOpen` to toggle `width: 32px` vs `$width`. Note it emits no width rule at all when `$width` is falsy, so without `width`/`searchWidth` the component never collapses.
251
248
  - `SearchInputContent` transitions `width: 0 → 100%` over `ANIMATION_DURATION` (0.1s) using `direction: rtl` for right-to-left expansion animation.
252
249
  - `SearchInner` applies blue border (`palette['blue-600']`) and blue background (`palette['blue-050']`) when `hasValue` or `alwaysHighlight`.
253
250
  - `Filter` chip is capped at `MAX_FILTER_WIDTH = 120px` and uses `palette['blue-600']` color.
@@ -263,7 +260,7 @@ import { SearchInput } from '@synerise/ds-search';
263
260
  - `@synerise/ds-tooltip` — wraps the search input and provides section header tooltips
264
261
  - `@synerise/ds-menu` — wraps items when `renderInMenu=true` (deprecated path)
265
262
  - `@synerise/ds-list-item` (`ListContextProvider`) — wraps items when `renderInMenu=false`
266
- - `antd/lib/input` — base `Input` component inside `SearchInput`
263
+ No antd — `SearchInput` renders a DS-native styled `input` (`S.SearchNativeInput`), which owns its own border/radius/background/focus chrome.
267
264
 
268
265
  ## Implementation notes
269
266
 
@@ -273,6 +270,7 @@ import { SearchInput } from '@synerise/ds-search';
273
270
  - **Arrow-key navigation**: `focusWithArrowKeys` from `ds-utils` moves focus across `.ds-search-item` elements; pressing Up when at the top returns focus to the input via `setFocusInputTrigger`.
274
271
  - **`toggleTrigger` / `focusTrigger`** on `SearchInput` are boolean flip-flops — the component reacts to a change in value (via `usePrevious`), not to the value itself.
275
272
  - **`width` prop is deprecated** — `searchWidth` is the current prop; both set the same `mergedWidth` internal variable.
273
+ - **Two components own the expanded width, and their formulas must match.** `SearchWrapper` (in `Search`) sets the outer width from `isExpanded = isOpen || !!value || !!label || !!alwaysExpanded`; `SearchInputContent` (in `SearchInput`) sets the inner width from `renderInputOpened = isInputOpen || !!value || !!filterLabel`. When the two disagree the 32px wrapper clips a `width: 100%` input — which is exactly what happened while `Search` initialised `isOpen` to a bare `false`: mounting with a value (or `alwaysExpanded`, or a value arriving after mount) painted a 32px stub until the first click. Keep both as derived expressions; do not reintroduce a second piece of state for "is it open". Note also that `handleClearValue` in `Search` must `setIsOpen(true)` because the clear button is a *sibling* of the input content, so clearing never reaches `handleInputClick`, while `SearchInput.handleClearValue` deliberately stays open and refocuses.
276
274
  - **Gesture-aware outside-click collapse**: both `SearchInput` and `Search` keep the default `useOnClickOutside` events (`mousedown`/`touchstart`) so the "should I close?" decision still happens at gesture start — but the state change that triggers the CSS width animation is deferred to the matching `mouseup`/`touchend` via a document-level one-shot listener tracked in `pendingCloseCleanupRef` (cleaned up on unmount or on a repeat mousedown). Rationale: firing the collapse on `mousedown` would shift adjacent layout between mousedown and mouseup, causing the browser to miss the `click` on the neighbouring element. A `['click']`-only variant was tried and rejected because some host apps cancel/stop-propagation on click events before they reach `document`, which breaks the close entirely.
277
275
  - **`getParametersScrollTop` takes `hasRecentTitle`**: callers must pass `hasRecentTitle: !!recentDisplayProps.title` so the function only subtracts `LIST_HEADER_HEIGHT` when the recent section actually renders a header. Without this, the parameters list scrolls 42 px out of sync when the recent section has no title.
278
276
  - `AnyObject = Record<string, any>` is used throughout for item shapes; TypeScript generics at the `Search` level provide partial type safety but `itemRender` and internal utilities rely on `any`.
package/dist/Search.js CHANGED
@@ -55,11 +55,13 @@ function Search({
55
55
  const [filteredSuggestions, setFilteredSuggestions] = useState(suggestions ?? null);
56
56
  const [isListVisible, setIsListVisible] = useState(false);
57
57
  const [isResultChosen, setIsResultChosen] = useState(false);
58
+ const [isOpen, setIsOpen] = useState(false);
58
59
  const [toggleInputTrigger, setToggleInputTrigger] = useState(false);
59
60
  const [focusInputTrigger, setFocusInputTrigger] = useState(false);
60
61
  const [scrollbarScrollTop, setScrollbarScrollTop] = useState(0);
61
62
  const [moveCursorToEnd, setMoveCursorToEnd] = useState(true);
62
- const [itemsListWidth, setItemsListWidth] = useState(width || searchWidth ? (width || searchWidth) - MENU_WIDTH_OFFSET : 0);
63
+ const mergedWidth = searchWidth !== void 0 ? searchWidth : width;
64
+ const [itemsListWidth, setItemsListWidth] = useState(mergedWidth ? Math.max(0, mergedWidth - MENU_WIDTH_OFFSET) : 0);
63
65
  useEffect(() => {
64
66
  if (parameterValue) {
65
67
  const found = parameters.find((param) => param[textLookupConfig.parameters] === parameterValue);
@@ -82,9 +84,8 @@ function Search({
82
84
  }
83
85
  }, [suggestions, value, textLookupConfig.suggestions]);
84
86
  useEffect(() => {
85
- const newWidth = (width || searchWidth || 0) - MENU_WIDTH_OFFSET;
86
- setItemsListWidth(Math.max(0, newWidth));
87
- }, [width, searchWidth]);
87
+ setItemsListWidth(Math.max(0, (mergedWidth || 0) - MENU_WIDTH_OFFSET));
88
+ }, [mergedWidth]);
88
89
  useEffect(() => {
89
90
  if (hideLabel) {
90
91
  setLabel(null);
@@ -108,29 +109,28 @@ function Search({
108
109
  const handleDropdownClick = () => {
109
110
  setFocusInputTrigger((prev) => !prev);
110
111
  };
111
- const mergedWidth = searchWidth !== void 0 ? searchWidth : width;
112
+ const isExpanded = isOpen || !!value || !!label || !!alwaysExpanded;
112
113
  const onKeyDown = (event) => {
113
- if (event.key === "Backspace" && value === "" && isOpen) {
114
+ if (event.key === "Backspace" && value === "" && isExpanded) {
114
115
  setLabel(null);
115
116
  setFilteredRecent(recent);
116
117
  onParameterValueChange?.("", null);
117
118
  return;
118
119
  }
119
- if (event.key === "Enter" && isOpen) {
120
+ if (event.key === "Enter" && isExpanded) {
120
121
  setIsResultChosen(!!value);
121
122
  }
122
123
  setMoveCursorToEnd(false);
123
124
  };
124
125
  const getSearchWrapperWidth = () => {
125
- if (width) {
126
- return width - MENU_WIDTH_OFFSET;
126
+ if (mergedWidth) {
127
+ return Math.max(0, mergedWidth - MENU_WIDTH_OFFSET);
127
128
  }
128
129
  if (wrapperRef.current && wrapperRef.current.clientWidth > MENU_WIDTH_OFFSET) {
129
130
  return wrapperRef.current.clientWidth - MENU_WIDTH_OFFSET;
130
131
  }
131
132
  return 0;
132
133
  };
133
- const [isOpen, setIsOpen] = useState(false);
134
134
  const pendingCloseCleanupRef = useRef(null);
135
135
  useEffect(() => () => {
136
136
  pendingCloseCleanupRef.current?.();
@@ -159,6 +159,7 @@ function Search({
159
159
  setFilteredParameters(parameters);
160
160
  setFilteredSuggestions(suggestions ?? null);
161
161
  setIsResultChosen(false);
162
+ setIsOpen(true);
162
163
  };
163
164
  const handleChange = (currentValue) => {
164
165
  onValueChange?.(currentValue);
@@ -230,9 +231,9 @@ function Search({
230
231
  };
231
232
  return /* @__PURE__ */ jsx(SearchInput, { alwaysHighlight: true, clearTooltip, searchTooltipProps, filterLookupKey, textLookupKey: String(textLookupConfig.parameters), filterLabel: label, focusTrigger: focusInputTrigger, onButtonClick: handleButtonClick, onChange: handleChange, onClear: handleClearValue, onClick: handleInputClick, onKeyDown, onToggle: handleToggle, placeholder, toggleTrigger: toggleInputTrigger, value, moveCursorToEnd, disableInput, inputProps, alwaysExpanded, closeOnClickOutside: !alwaysExpanded && !isListVisible && !value && !label });
232
233
  };
233
- return /* @__PURE__ */ jsxs(SearchWrapper, { ref: wrapperRef, className: "SearchWrapper", inputOpen: isOpen, $width: mergedWidth, onKeyDown: handleKeyDownWrapper, style, children: [
234
+ return /* @__PURE__ */ jsxs(SearchWrapper, { ref: wrapperRef, className: "SearchWrapper", inputOpen: isExpanded, $width: mergedWidth, onKeyDown: handleKeyDownWrapper, style, children: [
234
235
  renderInputWrapper(),
235
- isListVisible && /* @__PURE__ */ jsx(SearchDropdownWrapper, { dropdownWidth, onClick: handleDropdownClick, children: /* @__PURE__ */ jsx(SearchDropdownContent, { maxHeight: dropdownMaxHeight, className: isOpen && !isResultChosen && isListItemRendered() ? "search-list-open" : "", children: /* @__PURE__ */ jsxs(Scrollbar, { absolute: true, maxHeight: dropdownMaxHeight - SCROLLBAR_HEIGHT_OFFSET, onScroll: handleScroll, children: [
236
+ isListVisible && /* @__PURE__ */ jsx(SearchDropdownWrapper, { dropdownWidth, onClick: handleDropdownClick, children: /* @__PURE__ */ jsx(SearchDropdownContent, { maxHeight: dropdownMaxHeight, className: isExpanded && !isResultChosen && isListItemRendered() ? "search-list-open" : "", children: /* @__PURE__ */ jsxs(Scrollbar, { absolute: true, maxHeight: dropdownMaxHeight - SCROLLBAR_HEIGHT_OFFSET, onScroll: handleScroll, children: [
236
237
  shouldRenderRecent && /* @__PURE__ */ jsx(SearchRenderer, { data: filteredRecent, highlight: value, width: widthDropdownSearch, onItemClick: (item) => selectResult(item, SelectResultDataKeys.RECENT), ...recentDisplayProps }),
237
238
  !!filteredParameters?.length && !!filteredRecent?.length && !label && divider,
238
239
  shouldRenderParameters && /* @__PURE__ */ jsx(SearchRenderer, { data: filteredParameters, highlight: value, width: widthDropdownSearch, height: dropdownMaxHeight, listProps: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synerise/ds-search",
3
- "version": "1.5.35",
3
+ "version": "1.5.36",
4
4
  "description": "Search UI Component for the Synerise Design System",
5
5
  "license": "ISC",
6
6
  "repository": "Synerise/synerise-design",
@@ -58,5 +58,5 @@
58
58
  "styled-components": "^5.3.3",
59
59
  "vitest": "4"
60
60
  },
61
- "gitHead": "6df24ed12cd5f276d8eccde730591807982e3385"
61
+ "gitHead": "0a103024461277b2f410e19a88d437764017c1d1"
62
62
  }