@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 +6 -0
- package/CLAUDE.md +4 -6
- package/dist/Search.js +13 -12
- package/package.json +2 -2
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 —
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
86
|
-
|
|
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
|
|
112
|
+
const isExpanded = isOpen || !!value || !!label || !!alwaysExpanded;
|
|
112
113
|
const onKeyDown = (event) => {
|
|
113
|
-
if (event.key === "Backspace" && value === "" &&
|
|
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" &&
|
|
120
|
+
if (event.key === "Enter" && isExpanded) {
|
|
120
121
|
setIsResultChosen(!!value);
|
|
121
122
|
}
|
|
122
123
|
setMoveCursorToEnd(false);
|
|
123
124
|
};
|
|
124
125
|
const getSearchWrapperWidth = () => {
|
|
125
|
-
if (
|
|
126
|
-
return
|
|
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:
|
|
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:
|
|
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.
|
|
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": "
|
|
61
|
+
"gitHead": "0a103024461277b2f410e19a88d437764017c1d1"
|
|
62
62
|
}
|