diff --git a/dist/cjs/index.js b/dist/cjs/index.js index 740466a..61be6b7 100644 --- a/dist/cjs/index.js +++ b/dist/cjs/index.js @@ -191,11 +191,11 @@ const autocompleteLite = ({ !noAction && (onAction == null ? void 0 : onAction(item)); return true; } - onSelectChange(item); const itemValue = getItemValue(item); + if (!select) onChange(itemValue); const endIndex = itemValue.length; inputRef.current.setSelectionRange(endIndex, endIndex); - if (!select) onChange(itemValue); + onSelectChange(item); }; const resetState = shouldClose => { setFocusItem(); @@ -206,7 +206,7 @@ const autocompleteLite = ({ } }; return { - clearable: !!inputValue, + isInputEmpty: !inputValue, getClearProps: () => ({ tabIndex: -1, onMouseDown: startCapture, @@ -268,6 +268,7 @@ const autocompleteLite = ({ case 'Enter': if (open) { if (focusItem) { + e.preventDefault(); resetState(selectItemOrAction(focusItem)); } else if (!select) { resetState(true); @@ -389,7 +390,7 @@ const dropdownToggle = ({ return (_toggleRef$current = toggleRef.current) == null ? void 0 : _toggleRef$current.focus(); }, 0); return { - clearable: !!inputValue, + isInputEmpty: !inputValue, getToggleProps: () => ({ ref: toggleRef, onMouseDown: startToggle, diff --git a/dist/esm/features/atom/autocompleteLite.js b/dist/esm/features/atom/autocompleteLite.js index 5255b6d..1687438 100644 --- a/dist/esm/features/atom/autocompleteLite.js +++ b/dist/esm/features/atom/autocompleteLite.js @@ -37,11 +37,11 @@ const autocompleteLite = ({ !noAction && (onAction == null ? void 0 : onAction(item)); return true; } - onSelectChange(item); const itemValue = getItemValue(item); + if (!select) onChange(itemValue); const endIndex = itemValue.length; inputRef.current.setSelectionRange(endIndex, endIndex); - if (!select) onChange(itemValue); + onSelectChange(item); }; const resetState = shouldClose => { setFocusItem(); @@ -52,7 +52,7 @@ const autocompleteLite = ({ } }; return { - clearable: !!inputValue, + isInputEmpty: !inputValue, getClearProps: () => ({ tabIndex: -1, onMouseDown: startCapture, @@ -114,6 +114,7 @@ const autocompleteLite = ({ case 'Enter': if (open) { if (focusItem) { + e.preventDefault(); resetState(selectItemOrAction(focusItem)); } else if (!select) { resetState(true); diff --git a/dist/esm/features/atom/dropdownToggle.js b/dist/esm/features/atom/dropdownToggle.js index ace7332..c76f09e 100644 --- a/dist/esm/features/atom/dropdownToggle.js +++ b/dist/esm/features/atom/dropdownToggle.js @@ -23,7 +23,7 @@ const dropdownToggle = ({ return (_toggleRef$current = toggleRef.current) == null ? void 0 : _toggleRef$current.focus(); }, 0); return { - clearable: !!inputValue, + isInputEmpty: !inputValue, getToggleProps: () => ({ ref: toggleRef, onMouseDown: startToggle, diff --git a/examples/package-lock.json b/examples/package-lock.json index 140926f..0451afe 100644 --- a/examples/package-lock.json +++ b/examples/package-lock.json @@ -24,7 +24,7 @@ }, "..": { "name": "@szhsin/react-autocomplete", - "version": "0.9.2", + "version": "0.9.3", "license": "MIT", "devDependencies": { "@babel/core": "^7.23.2", diff --git a/examples/pages/dropdown.tsx b/examples/pages/dropdown.tsx index 04facb7..f39bd83 100644 --- a/examples/pages/dropdown.tsx +++ b/examples/pages/dropdown.tsx @@ -35,7 +35,7 @@ export default function Dropdown() { getItemProps, getToggleProps, getClearProps, - clearable, + isInputEmpty, open, focusItem, inputRef @@ -128,7 +128,7 @@ export default function Dropdown() { {...getInputProps()} placeholder="Search a state..." /> - {clearable && ( + {!isInputEmpty && ( diff --git a/examples/pages/multiSelect.tsx b/examples/pages/multiSelect.tsx index 20b0804..4831b6b 100644 --- a/examples/pages/multiSelect.tsx +++ b/examples/pages/multiSelect.tsx @@ -45,7 +45,7 @@ export default function Home() { getInputWrapperProps, open, focusItem, - clearable, + isInputEmpty, removeSelect, focused } = useMultiSelect({ @@ -134,7 +134,7 @@ export default function Home() {
- {clearable && ( + {!isInputEmpty && ( diff --git a/examples/pages/multiSelectAction.tsx b/examples/pages/multiSelectAction.tsx index 8d4388c..3d348b1 100644 --- a/examples/pages/multiSelectAction.tsx +++ b/examples/pages/multiSelectAction.tsx @@ -57,7 +57,7 @@ export default function Home() { getInputWrapperProps, open, focusItem, - clearable, + isInputEmpty, removeSelect, focused } = useMultiSelect({ @@ -155,7 +155,7 @@ export default function Home() {
- {clearable && ( + {!isInputEmpty && ( diff --git a/examples/pages/search.tsx b/examples/pages/search.tsx new file mode 100644 index 0000000..72ceb64 --- /dev/null +++ b/examples/pages/search.tsx @@ -0,0 +1,93 @@ +import React, { useState } from 'react'; +import { useCombobox, supercomplete, linearTraversal } from '@szhsin/react-autocomplete'; +import styles from '@/styles/Home.module.css'; +import { US_STATES } from '../data'; + +type Item = { name: string; abbr: string }; +const getItemValue = (item: Item) => item.name; + +const search = (value: string | undefined) => value && console.log(`Searching for "${value}"`); + +export default function Home() { + const [value, setValue] = useState(); + const items = value + ? US_STATES.filter((item) => item.name.toLowerCase().startsWith(value.toLowerCase())) + : US_STATES; + + const { + getInputProps, + getListProps, + getItemProps, + getToggleProps, + getClearProps, + open, + focusItem, + isInputEmpty + } = useCombobox({ + onSelectChange: (selected) => search(selected?.name), + getItemValue, + value, + onChange: setValue, + feature: supercomplete({ + selectOnBlur: false, + getFocusItem: (newValue) => + US_STATES.filter((item) => + item.name.toLowerCase().startsWith(newValue.toLowerCase()) + )[0] + }), + + traversal: linearTraversal({ items, traverseInput: true }) + }); + + return ( +
+
value: {value}
+
Focus item: {focusItem?.name}
+ +
{ + e.preventDefault(); + search(value); + }} + > + + + {!isInputEmpty && ( + + )} + +
+
    + {items.map((item) => ( +
  • + {item.name} +
  • + ))} +
+
+ ); +} diff --git a/package-lock.json b/package-lock.json index a5d752f..57e39bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@szhsin/react-autocomplete", - "version": "0.9.2", + "version": "0.9.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@szhsin/react-autocomplete", - "version": "0.9.2", + "version": "0.9.3", "license": "MIT", "devDependencies": { "@babel/core": "^7.23.2", diff --git a/package.json b/package.json index e28d2e0..b9cf5bc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@szhsin/react-autocomplete", - "version": "0.9.2", + "version": "0.9.3", "description": "", "author": "Zheng Song", "license": "MIT", diff --git a/src/common.ts b/src/common.ts index 22cb2cd..18d7d90 100644 --- a/src/common.ts +++ b/src/common.ts @@ -57,8 +57,8 @@ export interface Contextual setTmpValue: (value?: string | undefined) => void; } -export interface Clearable { - clearable: boolean; +export interface FeatureState { + isInputEmpty: boolean; } export interface TraversalProps { diff --git a/src/features/atom/autocompleteLite.ts b/src/features/atom/autocompleteLite.ts index 922b6ec..4e41ebe 100644 --- a/src/features/atom/autocompleteLite.ts +++ b/src/features/atom/autocompleteLite.ts @@ -3,7 +3,7 @@ import type { GetPropsFunctions, GetPropsWithRefFunctions, AutocompleteFeatureProps, - Clearable + FeatureState } from '../../common'; import { useFocusCapture } from '../../hooks/useFocusCapture'; @@ -11,7 +11,7 @@ type AutocompleteLiteFeature = Feature< T, Pick, 'getListProps' | 'getItemProps' | 'getClearProps'> & Pick, 'getInputProps'> & - Clearable + FeatureState >; const scrollIntoView = (element: HTMLElement | null) => @@ -55,11 +55,13 @@ const autocompleteLite = return true; // Always close list on action } - onSelectChange(item); const itemValue = getItemValue(item); + if (!select) onChange(itemValue); const endIndex = itemValue.length; inputRef.current!.setSelectionRange(endIndex, endIndex); - if (!select) onChange(itemValue); + // We place onSelectChange after onChange to give user an opportunity + // to manipulate the `value` state + onSelectChange(item); }; const resetState = (shouldClose?: boolean) => { @@ -72,7 +74,7 @@ const autocompleteLite = }; return { - clearable: !!inputValue, + isInputEmpty: !inputValue, getClearProps: () => ({ tabIndex: -1, @@ -145,6 +147,8 @@ const autocompleteLite = case 'Enter': if (open) { if (focusItem) { + // Call preventDefault as we've already triggered on* events in this branch + e.preventDefault(); resetState(selectItemOrAction(focusItem)); } else if (!select) { resetState(true); diff --git a/src/features/atom/dropdownToggle.ts b/src/features/atom/dropdownToggle.ts index bc583d5..ce42f8b 100644 --- a/src/features/atom/dropdownToggle.ts +++ b/src/features/atom/dropdownToggle.ts @@ -4,7 +4,7 @@ import type { FeatureProps, GetPropsFunctions, GetPropsWithRefFunctions, - Clearable + FeatureState } from '../../common'; import { useToggle } from '../../hooks/useToggle'; @@ -12,7 +12,7 @@ type DropdownToggleFeature = Feature< T, Pick, 'getToggleProps'> & Pick, 'getInputProps'> & - Clearable + FeatureState >; const dropdownToggle = @@ -33,7 +33,7 @@ const dropdownToggle = const focusToggle = () => setTimeout(() => toggleRef.current?.focus(), 0); return { - clearable: !!inputValue, + isInputEmpty: !inputValue, getToggleProps: () => ({ ref: toggleRef, diff --git a/types/common.d.ts b/types/common.d.ts index 12095e0..0456141 100644 --- a/types/common.d.ts +++ b/types/common.d.ts @@ -42,8 +42,8 @@ export interface Contextual extends PassthroughProps, AdapterProps, Equ tmpValue?: string; setTmpValue: (value?: string | undefined) => void; } -export interface Clearable { - clearable: boolean; +export interface FeatureState { + isInputEmpty: boolean; } export interface TraversalProps { traverseInput?: boolean; diff --git a/types/features/atom/autocompleteLite.d.ts b/types/features/atom/autocompleteLite.d.ts index b3101ff..a208811 100644 --- a/types/features/atom/autocompleteLite.d.ts +++ b/types/features/atom/autocompleteLite.d.ts @@ -1,4 +1,4 @@ -import type { Feature, GetPropsFunctions, GetPropsWithRefFunctions, AutocompleteFeatureProps, Clearable } from '../../common'; -type AutocompleteLiteFeature = Feature, 'getListProps' | 'getItemProps' | 'getClearProps'> & Pick, 'getInputProps'> & Clearable>; +import type { Feature, GetPropsFunctions, GetPropsWithRefFunctions, AutocompleteFeatureProps, FeatureState } from '../../common'; +type AutocompleteLiteFeature = Feature, 'getListProps' | 'getItemProps' | 'getClearProps'> & Pick, 'getInputProps'> & FeatureState>; declare const autocompleteLite: ({ rovingText, select, selectOnBlur, deselectOnClear, deselectOnChange, closeOnSelect }?: AutocompleteFeatureProps) => AutocompleteLiteFeature; export { type AutocompleteLiteFeature, autocompleteLite }; diff --git a/types/features/atom/dropdownToggle.d.ts b/types/features/atom/dropdownToggle.d.ts index 8fe6aca..2ec4fa5 100644 --- a/types/features/atom/dropdownToggle.d.ts +++ b/types/features/atom/dropdownToggle.d.ts @@ -1,4 +1,4 @@ -import type { Feature, FeatureProps, GetPropsFunctions, GetPropsWithRefFunctions, Clearable } from '../../common'; -type DropdownToggleFeature = Feature, 'getToggleProps'> & Pick, 'getInputProps'> & Clearable>; +import type { Feature, FeatureProps, GetPropsFunctions, GetPropsWithRefFunctions, FeatureState } from '../../common'; +type DropdownToggleFeature = Feature, 'getToggleProps'> & Pick, 'getInputProps'> & FeatureState>; declare const dropdownToggle: ({ closeOnSelect }: Pick, "closeOnSelect">) => DropdownToggleFeature; export { type DropdownToggleFeature, dropdownToggle };