fix: replace isDeepStrictEqual with navigation-aware options comparison (#507)
The select cursor highlight was broken because isDeepStrictEqual in use-select-navigation.ts and use-multi-select-state.ts would fail when options contained identity-unstable properties (JSX label elements, function onChange callbacks, computed disabled booleans). This caused the reset logic to fire on every re-render, resetting focusedValue back to the first option. Replace isDeepStrictEqual with optionsNavigateEqual which only compares properties that affect navigation behavior: value, disabled, and type. ReactNode labels and function callbacks are intentionally excluded as they are identity-unstable but don't change navigation semantics. Fixes #472 Co-authored-by: OpenClaude Worker 3 <worker-3@openclaude.local>
This commit is contained in:
@@ -6,10 +6,34 @@ import {
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { isDeepStrictEqual } from 'util'
|
||||
import OptionMap from './option-map.js'
|
||||
import type { OptionWithDescription } from './select.js'
|
||||
|
||||
/**
|
||||
* Compare two option arrays for structural equality on properties that
|
||||
* affect navigation behavior. ReactNode `label` and function `onChange`
|
||||
* are intentionally excluded — they are identity-unstable (new reference
|
||||
* each render) but don't change navigation semantics.
|
||||
*/
|
||||
export function optionsNavigateEqual<T>(
|
||||
a: OptionWithDescription<T>[],
|
||||
b: OptionWithDescription<T>[],
|
||||
): boolean {
|
||||
if (a.length !== b.length) return false
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
const ao = a[i]!
|
||||
const bo = b[i]!
|
||||
if (
|
||||
ao.value !== bo.value ||
|
||||
ao.disabled !== bo.disabled ||
|
||||
ao.type !== bo.type
|
||||
) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type State<T> = {
|
||||
/**
|
||||
* Map where key is option's value and value is option's index.
|
||||
@@ -524,7 +548,7 @@ export function useSelectNavigation<T>({
|
||||
|
||||
const [lastOptions, setLastOptions] = useState(options)
|
||||
|
||||
if (options !== lastOptions && !isDeepStrictEqual(options, lastOptions)) {
|
||||
if (options !== lastOptions && !optionsNavigateEqual(options, lastOptions)) {
|
||||
dispatch({
|
||||
type: 'reset',
|
||||
state: createDefaultState({
|
||||
|
||||
Reference in New Issue
Block a user