@noya-app/noya-designsystem 0.1.74 → 0.1.75
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/.turbo/turbo-build.log +10 -10
- package/.turbo/turbo-lint.log +15 -1
- package/CHANGELOG.md +6 -0
- package/dist/index.d.mts +4 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +61 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +61 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/combobox.test.ts +18 -0
- package/src/components/Combobox.tsx +48 -5
- package/src/utils/combobox.ts +41 -2
package/package.json
CHANGED
|
@@ -156,6 +156,24 @@ test("resets state", () => {
|
|
|
156
156
|
expect(snapshot.selectedIndex).toBe(0);
|
|
157
157
|
});
|
|
158
158
|
|
|
159
|
+
test("reset keeps selection aligned to provided title", () => {
|
|
160
|
+
const state = new ComboboxState(testItems, "", true);
|
|
161
|
+
state.setFilter("font");
|
|
162
|
+
state.moveSelection("down");
|
|
163
|
+
state.selectCurrentItem();
|
|
164
|
+
|
|
165
|
+
state.reset("", { selectedItemTitle: "font-italic" });
|
|
166
|
+
const snapshot = state.getSnapshot();
|
|
167
|
+
|
|
168
|
+
const matchedIndex = snapshot.filteredItems.findIndex(
|
|
169
|
+
(item) =>
|
|
170
|
+
isSelectableMenuItem(item) &&
|
|
171
|
+
item.title?.toString() === "font-italic"
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
expect(snapshot.selectedIndex).toBe(matchedIndex);
|
|
175
|
+
});
|
|
176
|
+
|
|
159
177
|
test("handles empty query", () => {
|
|
160
178
|
const state = new ComboboxState(testItems, "", true);
|
|
161
179
|
state.setFilter("");
|
|
@@ -154,6 +154,17 @@ export const Combobox = memoGeneric(
|
|
|
154
154
|
|
|
155
155
|
const initialValueString = getInitialValueString();
|
|
156
156
|
|
|
157
|
+
const resetSelectionOptions = useMemo(
|
|
158
|
+
() =>
|
|
159
|
+
props.mode === "string"
|
|
160
|
+
? { selectedItemTitle: initialValueString }
|
|
161
|
+
: {
|
|
162
|
+
selectedItemTitle: initialValueString,
|
|
163
|
+
selectedItemValue: props.value?.value?.toString(),
|
|
164
|
+
},
|
|
165
|
+
[initialValueString, props.mode, props.value]
|
|
166
|
+
);
|
|
167
|
+
|
|
157
168
|
const [comboboxState] = useState(
|
|
158
169
|
() =>
|
|
159
170
|
new ComboboxState(
|
|
@@ -165,6 +176,7 @@ export const Combobox = memoGeneric(
|
|
|
165
176
|
const state = useComboboxState(comboboxState);
|
|
166
177
|
const [isFocused, setIsFocused] = useState(false);
|
|
167
178
|
const listRef = useRef<ListView.VirtualizedList>(null);
|
|
179
|
+
const menuOpenedRef = useRef(false);
|
|
168
180
|
const [shouldBlur, setShouldBlur] = useState(false);
|
|
169
181
|
const { fieldId: id } = useLabel({
|
|
170
182
|
fieldId: rest.id,
|
|
@@ -190,9 +202,24 @@ export const Combobox = memoGeneric(
|
|
|
190
202
|
}
|
|
191
203
|
}, [selectedIndex]);
|
|
192
204
|
|
|
205
|
+
useEffect(() => {
|
|
206
|
+
if (!shouldShowMenu) {
|
|
207
|
+
menuOpenedRef.current = false;
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (menuOpenedRef.current) return;
|
|
212
|
+
|
|
213
|
+
menuOpenedRef.current = true;
|
|
214
|
+
|
|
215
|
+
if (listRef.current) {
|
|
216
|
+
listRef.current.scrollToIndex(selectedIndex);
|
|
217
|
+
}
|
|
218
|
+
}, [shouldShowMenu, selectedIndex]);
|
|
219
|
+
|
|
193
220
|
const handleBlur = useCallback(() => {
|
|
194
221
|
ref.current?.blur();
|
|
195
|
-
comboboxState.reset(initialValueString);
|
|
222
|
+
comboboxState.reset(initialValueString, resetSelectionOptions);
|
|
196
223
|
if (props.mode === "string") {
|
|
197
224
|
props.onBlur?.(
|
|
198
225
|
state.filter === state.lastSubmittedValue.filter,
|
|
@@ -208,13 +235,21 @@ export const Combobox = memoGeneric(
|
|
|
208
235
|
}
|
|
209
236
|
}
|
|
210
237
|
setIsFocused(false);
|
|
211
|
-
}, [
|
|
238
|
+
}, [
|
|
239
|
+
filter,
|
|
240
|
+
initialValueString,
|
|
241
|
+
props,
|
|
242
|
+
resetSelectionOptions,
|
|
243
|
+
state,
|
|
244
|
+
comboboxState,
|
|
245
|
+
]);
|
|
212
246
|
|
|
213
247
|
const handleFocus: FocusEventHandler<HTMLInputElement> = useCallback(
|
|
214
248
|
(event) => {
|
|
215
249
|
setIsFocused(true);
|
|
216
250
|
comboboxState.reset(
|
|
217
|
-
openMenuBehavior === "showAllItems" ? "" : initialValueString
|
|
251
|
+
openMenuBehavior === "showAllItems" ? "" : initialValueString,
|
|
252
|
+
resetSelectionOptions
|
|
218
253
|
);
|
|
219
254
|
if (ref.current) {
|
|
220
255
|
const length = ref.current.value.length;
|
|
@@ -222,7 +257,13 @@ export const Combobox = memoGeneric(
|
|
|
222
257
|
}
|
|
223
258
|
onFocus?.(event);
|
|
224
259
|
},
|
|
225
|
-
[
|
|
260
|
+
[
|
|
261
|
+
initialValueString,
|
|
262
|
+
onFocus,
|
|
263
|
+
openMenuBehavior,
|
|
264
|
+
resetSelectionOptions,
|
|
265
|
+
comboboxState,
|
|
266
|
+
]
|
|
226
267
|
);
|
|
227
268
|
|
|
228
269
|
const handleUpdateSelection = useCallback(
|
|
@@ -415,7 +456,8 @@ export const Combobox = memoGeneric(
|
|
|
415
456
|
|
|
416
457
|
// Use openMenuBehavior consistently for both focus and chevron click
|
|
417
458
|
comboboxState.reset(
|
|
418
|
-
openMenuBehavior === "showAllItems" ? "" : initialValueString
|
|
459
|
+
openMenuBehavior === "showAllItems" ? "" : initialValueString,
|
|
460
|
+
resetSelectionOptions
|
|
419
461
|
);
|
|
420
462
|
ref.current?.focus();
|
|
421
463
|
},
|
|
@@ -424,6 +466,7 @@ export const Combobox = memoGeneric(
|
|
|
424
466
|
openMenuBehavior,
|
|
425
467
|
initialValueString,
|
|
426
468
|
handleBlur,
|
|
469
|
+
resetSelectionOptions,
|
|
427
470
|
comboboxState,
|
|
428
471
|
]
|
|
429
472
|
);
|
package/src/utils/combobox.ts
CHANGED
|
@@ -203,9 +203,48 @@ export class ComboboxState<T extends string> {
|
|
|
203
203
|
this.notifyChange();
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
-
reset(
|
|
206
|
+
reset(
|
|
207
|
+
newFilter = "",
|
|
208
|
+
options?: {
|
|
209
|
+
selectedItemTitle?: string;
|
|
210
|
+
selectedItemValue?: string | number;
|
|
211
|
+
}
|
|
212
|
+
) {
|
|
207
213
|
this.filter = newFilter;
|
|
208
|
-
|
|
214
|
+
|
|
215
|
+
const filteredItems = this.getFilteredItems();
|
|
216
|
+
const normalizedTitle = options?.selectedItemTitle
|
|
217
|
+
? options.selectedItemTitle.toString().toLowerCase()
|
|
218
|
+
: undefined;
|
|
219
|
+
const normalizedValue = options?.selectedItemValue
|
|
220
|
+
? options.selectedItemValue.toString().toLowerCase()
|
|
221
|
+
: undefined;
|
|
222
|
+
|
|
223
|
+
const matchedIndex =
|
|
224
|
+
normalizedTitle || normalizedValue
|
|
225
|
+
? filteredItems.findIndex((item) => {
|
|
226
|
+
if (!isSelectableMenuItem(item)) return false;
|
|
227
|
+
|
|
228
|
+
const title = item.title?.toString().toLowerCase();
|
|
229
|
+
const value = item.value?.toString().toLowerCase();
|
|
230
|
+
|
|
231
|
+
if (normalizedValue && value === normalizedValue) return true;
|
|
232
|
+
if (normalizedTitle && title === normalizedTitle) return true;
|
|
233
|
+
return false;
|
|
234
|
+
})
|
|
235
|
+
: -1;
|
|
236
|
+
|
|
237
|
+
if (matchedIndex !== -1) {
|
|
238
|
+
this.selectedIndex = matchedIndex;
|
|
239
|
+
this.notifyChange();
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const firstSelectableIndex = filteredItems.findIndex((item): boolean =>
|
|
244
|
+
isSelectableMenuItem(item)
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
this.selectedIndex = firstSelectableIndex === -1 ? 0 : firstSelectableIndex;
|
|
209
248
|
this.notifyChange();
|
|
210
249
|
}
|
|
211
250
|
|