From 82675dc429bbc3dc021a9b30e3cc3ef6bde51708 Mon Sep 17 00:00:00 2001 From: Zheng Song Date: Fri, 21 Jun 2024 01:37:31 +1000 Subject: [PATCH 1/8] Do not filter items after close --- dist/cjs/index.js | 70 +++++++++++----------- dist/esm/features/atom/autocompleteLite.js | 46 +++++++------- dist/esm/features/atom/dropdownToggle.js | 14 ++--- dist/esm/features/molecule/dropdown.js | 2 +- dist/esm/hooks/useAutocomplete.js | 8 ++- examples/pages/dropdownExample.tsx | 6 +- examples/pages/index.tsx | 16 ++--- src/common.ts | 12 ++-- src/features/atom/autocompleteLite.ts | 54 ++++++++--------- src/features/atom/dropdownToggle.ts | 21 +++---- src/features/molecule/dropdown.ts | 2 +- src/features/molecule/supercomplete.ts | 2 +- src/hooks/useAutocomplete.ts | 8 ++- types/common.d.ts | 11 ++-- types/features/atom/autocompleteLite.d.ts | 8 +-- types/features/atom/dropdownToggle.d.ts | 4 +- types/features/molecule/supercomplete.d.ts | 2 +- 17 files changed, 146 insertions(+), 140 deletions(-) diff --git a/dist/cjs/index.js b/dist/cjs/index.js index 841bc97..9ed9916 100644 --- a/dist/cjs/index.js +++ b/dist/cjs/index.js @@ -3,8 +3,8 @@ var react = require('react'); const useAutocomplete = ({ - value = '', - onChange = () => {}, + value, + onChange, isItemDisabled = () => false, feature: useFeature, traversal: useTraversal, @@ -30,7 +30,9 @@ const useAutocomplete = ({ getItemValue, isItemDisabled, value, - onChange, + onChange: newValue => { + if (value != newValue) onChange == null || onChange(newValue); + }, inputRef, ...state }; @@ -86,7 +88,7 @@ const autocompleteLite = ({ rovingText, constricted, selectOnBlur = true, - deselectOnBlur = true + deselectOnClear = true } = {}) => ({ getItemValue, isItemDisabled, @@ -103,13 +105,13 @@ const autocompleteLite = ({ setOpen, inputRef }) => { + var _ref; const mutable = useMutableState({}); - const inputValue = tmpValue || value; - const updateValue = (newValue, moveCaretToEnd = true) => { - setTmpValue(); + const inputValue = (_ref = tmpValue || value) != null ? _ref : getItemValue(selectedItem); + const updateValue = newValue => { const endIndex = newValue.length; - moveCaretToEnd && inputRef.current.setSelectionRange(endIndex, endIndex); - if (value != newValue) onChange(newValue); + inputRef.current.setSelectionRange(endIndex, endIndex); + if (!constricted) onChange(newValue); }; const updateItem = item => item !== selectedItem && setSelectedItem(item); const updateAll = item => { @@ -119,6 +121,8 @@ const autocompleteLite = ({ const closeList = () => { setOpen(false); setFocusItem(); + setTmpValue(); + if (constricted) onChange(); }; return { clearable: !!inputValue, @@ -130,9 +134,11 @@ const autocompleteLite = ({ onClick: () => { var _inputRef$current; (_inputRef$current = inputRef.current) == null || _inputRef$current.focus(); - updateValue(''); - setFocusItem(); setOpen(true); + onChange(''); + setTmpValue(); + setFocusItem(); + if (deselectOnClear) setSelectedItem(); } }), getListProps: () => ({ @@ -155,9 +161,16 @@ const autocompleteLite = ({ ref: inputRef, value: inputValue, onChange: e => { - setFocusItem(); setOpen(true); - updateValue(e.target.value, false); + setFocusItem(); + setTmpValue(); + const newValue = e.target.value; + onChange(newValue); + if (constricted) { + if (deselectOnClear && !newValue) setSelectedItem(); + } else if (newValue !== getItemValue(selectedItem)) { + setSelectedItem(); + } }, onBlur: ({ target @@ -170,12 +183,7 @@ const autocompleteLite = ({ if (!open) return; if (selectOnBlur && focusItem) { updateAll(focusItem); - } else if (constricted) { - if (value || !deselectOnBlur) updateAll(selectedItem);else updateItem(); - } else if (getItemValue(selectedItem) != value) { - updateItem(); } - setTmpValue(); closeList(); }, onKeyDown: e => { @@ -197,15 +205,7 @@ const autocompleteLite = ({ } break; case 'Escape': - if (open) { - if (constricted) { - updateAll(selectedItem); - } else if (!value || getItemValue(selectedItem) != value) { - updateItem(); - updateValue(value); - } - closeList(); - } + if (open) closeList(); break; } }, @@ -280,23 +280,22 @@ const dropdownToggle = () => ({ open, setOpen, focusItem, - onChange + value, + tmpValue }) => { const mutable = useMutableState({}); const toggleRef = react.useRef(null); + const inputValue = tmpValue || value || ''; react.useEffect(() => { var _inputRef$current; if (open) (_inputRef$current = inputRef.current) == null || _inputRef$current.focus(); }, [open, inputRef]); - const openList = () => { - onChange(''); - setOpen(true); - }; const focusToggle = () => setTimeout(() => { var _toggleRef$current; return (_toggleRef$current = toggleRef.current) == null ? void 0 : _toggleRef$current.focus(); }, 0); return { + clearable: !!inputValue, getToggleProps: () => ({ ref: toggleRef, onMouseDown: () => { @@ -306,7 +305,7 @@ const dropdownToggle = () => ({ if (mutable.a) { mutable.a = 0; } else { - openList(); + setOpen(true); } }, onKeyDown: e => { @@ -315,11 +314,12 @@ const dropdownToggle = () => ({ } = e; if (key === 'ArrowDown') { e.preventDefault(); - openList(); + setOpen(true); } } }), getInputProps: () => ({ + value: inputValue, onKeyDown: e => { const { key @@ -338,7 +338,7 @@ const dropdown = props => mergeFeatures(autocompleteLite({ ...props, constricted: true, selectOnBlur: false, - deselectOnBlur: false + deselectOnClear: false }), dropdownToggle()); const inline = ({ diff --git a/dist/esm/features/atom/autocompleteLite.js b/dist/esm/features/atom/autocompleteLite.js index 43cfce3..ca6287f 100644 --- a/dist/esm/features/atom/autocompleteLite.js +++ b/dist/esm/features/atom/autocompleteLite.js @@ -7,7 +7,7 @@ const autocompleteLite = ({ rovingText, constricted, selectOnBlur = true, - deselectOnBlur = true + deselectOnClear = true } = {}) => ({ getItemValue, isItemDisabled, @@ -24,13 +24,13 @@ const autocompleteLite = ({ setOpen, inputRef }) => { + var _ref; const mutable = useMutableState({}); - const inputValue = tmpValue || value; - const updateValue = (newValue, moveCaretToEnd = true) => { - setTmpValue(); + const inputValue = (_ref = tmpValue || value) != null ? _ref : getItemValue(selectedItem); + const updateValue = newValue => { const endIndex = newValue.length; - moveCaretToEnd && inputRef.current.setSelectionRange(endIndex, endIndex); - if (value != newValue) onChange(newValue); + inputRef.current.setSelectionRange(endIndex, endIndex); + if (!constricted) onChange(newValue); }; const updateItem = item => item !== selectedItem && setSelectedItem(item); const updateAll = item => { @@ -40,6 +40,8 @@ const autocompleteLite = ({ const closeList = () => { setOpen(false); setFocusItem(); + setTmpValue(); + if (constricted) onChange(); }; return { clearable: !!inputValue, @@ -51,9 +53,11 @@ const autocompleteLite = ({ onClick: () => { var _inputRef$current; (_inputRef$current = inputRef.current) == null || _inputRef$current.focus(); - updateValue(''); - setFocusItem(); setOpen(true); + onChange(''); + setTmpValue(); + setFocusItem(); + if (deselectOnClear) setSelectedItem(); } }), getListProps: () => ({ @@ -76,9 +80,16 @@ const autocompleteLite = ({ ref: inputRef, value: inputValue, onChange: e => { - setFocusItem(); setOpen(true); - updateValue(e.target.value, false); + setFocusItem(); + setTmpValue(); + const newValue = e.target.value; + onChange(newValue); + if (constricted) { + if (deselectOnClear && !newValue) setSelectedItem(); + } else if (newValue !== getItemValue(selectedItem)) { + setSelectedItem(); + } }, onBlur: ({ target @@ -91,12 +102,7 @@ const autocompleteLite = ({ if (!open) return; if (selectOnBlur && focusItem) { updateAll(focusItem); - } else if (constricted) { - if (value || !deselectOnBlur) updateAll(selectedItem);else updateItem(); - } else if (getItemValue(selectedItem) != value) { - updateItem(); } - setTmpValue(); closeList(); }, onKeyDown: e => { @@ -118,15 +124,7 @@ const autocompleteLite = ({ } break; case 'Escape': - if (open) { - if (constricted) { - updateAll(selectedItem); - } else if (!value || getItemValue(selectedItem) != value) { - updateItem(); - updateValue(value); - } - closeList(); - } + if (open) closeList(); break; } }, diff --git a/dist/esm/features/atom/dropdownToggle.js b/dist/esm/features/atom/dropdownToggle.js index e6e51e2..0d02a5a 100644 --- a/dist/esm/features/atom/dropdownToggle.js +++ b/dist/esm/features/atom/dropdownToggle.js @@ -6,23 +6,22 @@ const dropdownToggle = () => ({ open, setOpen, focusItem, - onChange + value, + tmpValue }) => { const mutable = useMutableState({}); const toggleRef = useRef(null); + const inputValue = tmpValue || value || ''; useEffect(() => { var _inputRef$current; if (open) (_inputRef$current = inputRef.current) == null || _inputRef$current.focus(); }, [open, inputRef]); - const openList = () => { - onChange(''); - setOpen(true); - }; const focusToggle = () => setTimeout(() => { var _toggleRef$current; return (_toggleRef$current = toggleRef.current) == null ? void 0 : _toggleRef$current.focus(); }, 0); return { + clearable: !!inputValue, getToggleProps: () => ({ ref: toggleRef, onMouseDown: () => { @@ -32,7 +31,7 @@ const dropdownToggle = () => ({ if (mutable.a) { mutable.a = 0; } else { - openList(); + setOpen(true); } }, onKeyDown: e => { @@ -41,11 +40,12 @@ const dropdownToggle = () => ({ } = e; if (key === 'ArrowDown') { e.preventDefault(); - openList(); + setOpen(true); } } }), getInputProps: () => ({ + value: inputValue, onKeyDown: e => { const { key diff --git a/dist/esm/features/molecule/dropdown.js b/dist/esm/features/molecule/dropdown.js index 1919bfb..a97943d 100644 --- a/dist/esm/features/molecule/dropdown.js +++ b/dist/esm/features/molecule/dropdown.js @@ -6,7 +6,7 @@ const dropdown = props => mergeFeatures(autocompleteLite({ ...props, constricted: true, selectOnBlur: false, - deselectOnBlur: false + deselectOnClear: false }), dropdownToggle()); export { dropdown }; diff --git a/dist/esm/hooks/useAutocomplete.js b/dist/esm/hooks/useAutocomplete.js index 35d9a83..05d340e 100644 --- a/dist/esm/hooks/useAutocomplete.js +++ b/dist/esm/hooks/useAutocomplete.js @@ -1,8 +1,8 @@ import { useRef, useState, useCallback } from 'react'; const useAutocomplete = ({ - value = '', - onChange = () => {}, + value, + onChange, isItemDisabled = () => false, feature: useFeature, traversal: useTraversal, @@ -28,7 +28,9 @@ const useAutocomplete = ({ getItemValue, isItemDisabled, value, - onChange, + onChange: newValue => { + if (value != newValue) onChange == null || onChange(newValue); + }, inputRef, ...state }; diff --git a/examples/pages/dropdownExample.tsx b/examples/pages/dropdownExample.tsx index 037ffc3..5fb10b0 100644 --- a/examples/pages/dropdownExample.tsx +++ b/examples/pages/dropdownExample.tsx @@ -12,7 +12,7 @@ type Item = { name: string; abbr: string }; const getItemValue = (item: Item) => item.name; const isItemDisabled = ({ abbr }: Item) => abbr.startsWith('CO'); -const getGroupedItems = (value: string) => +const getGroupedItems = (value: string = '') => LIST_GROUP.map((group) => ({ ...group, states: group.states.filter((item) => item.name.toLowerCase().startsWith(value.toLowerCase())) @@ -20,7 +20,7 @@ const getGroupedItems = (value: string) => export default function Dropdown() { const [rovingText, setRovingText] = useState(false); - const [value, setValue] = useState(''); + const [value, setValue] = useState(''); // const items = US_STATES.filter((item) => item.name.toLowerCase().startsWith(value.toLowerCase())); // const [myinput, setmyinput] = useState(''); // const [items, setItems] = useState(US_STATES); @@ -109,7 +109,7 @@ export default function Dropdown() { }} >
- + {clearable && (
diff --git a/src/common.ts b/src/common.ts index 9c66efb..4b7b9dc 100644 --- a/src/common.ts +++ b/src/common.ts @@ -31,8 +31,8 @@ export interface AutocompleteState { export interface ContextualProps { isItemDisabled: (item: T) => boolean; - value: string; - onChange: (value: string) => void; + value: string | undefined; + onChange: (value?: string | undefined) => void; } export interface Contextual extends ContextualProps, AutocompleteState { @@ -42,6 +42,10 @@ export interface Contextual extends ContextualProps, AutocompleteState getItemValue: (item: T | undefined | null) => string; } +export interface Clearable { + clearable: boolean; +} + export interface TraversalProps { traverseInput?: boolean; } @@ -54,7 +58,7 @@ export interface FeatureProps { rovingText?: boolean; constricted?: boolean; selectOnBlur?: boolean; - deselectOnBlur?: boolean; + deselectOnClear?: boolean; getInlineItem: ( value: string ) => T | undefined | null | void | Promise; @@ -62,7 +66,7 @@ export interface FeatureProps { export type AutocompleteFeatureProps = Pick< FeatureProps, - 'rovingText' | 'constricted' | 'selectOnBlur' | 'deselectOnBlur' + 'rovingText' | 'constricted' | 'selectOnBlur' | 'deselectOnClear' >; export type Feature = ( diff --git a/src/features/atom/autocompleteLite.ts b/src/features/atom/autocompleteLite.ts index 9072472..c268550 100644 --- a/src/features/atom/autocompleteLite.ts +++ b/src/features/atom/autocompleteLite.ts @@ -2,14 +2,16 @@ import type { Feature, GetPropsFunctions, GetPropsWithRefFunctions, - AutocompleteFeatureProps + AutocompleteFeatureProps, + Clearable } from '../../common'; import { useMutableState } from '../../hooks/useMutableState'; type AutocompleteLiteFeature = Feature< T, Pick, 'getListProps' | 'getItemProps' | 'getClearProps'> & - Pick, 'getInputProps'> & { clearable: boolean } + Pick, 'getInputProps'> & + Clearable >; interface MutableState { @@ -28,7 +30,7 @@ const autocompleteLite = rovingText, constricted, selectOnBlur = true, - deselectOnBlur = true + deselectOnClear = true }: AutocompleteFeatureProps = {}): AutocompleteLiteFeature => ({ getItemValue, @@ -48,14 +50,12 @@ const autocompleteLite = }) => { const mutable = useMutableState({}); - const inputValue = tmpValue || value; + const inputValue = (tmpValue || value) ?? getItemValue(selectedItem); - const updateValue = (newValue: string, moveCaretToEnd: boolean = true) => { - setTmpValue(); + const updateValue = (newValue: string) => { const endIndex = newValue.length; - moveCaretToEnd && inputRef.current!.setSelectionRange(endIndex, endIndex); - - if (value != newValue) onChange(newValue); + inputRef.current!.setSelectionRange(endIndex, endIndex); + if (!constricted) onChange(newValue); }; const updateItem = (item?: T) => item !== selectedItem && setSelectedItem(item); @@ -68,6 +68,8 @@ const autocompleteLite = const closeList = () => { setOpen(false); setFocusItem(); + setTmpValue(); + if (constricted) onChange(); }; return { @@ -82,9 +84,11 @@ const autocompleteLite = onClick: () => { inputRef.current?.focus(); - updateValue(''); - setFocusItem(); setOpen(true); + onChange(''); + setTmpValue(); + setFocusItem(); + if (deselectOnClear) setSelectedItem(); } }), @@ -110,9 +114,17 @@ const autocompleteLite = value: inputValue, onChange: (e) => { - setFocusItem(); setOpen(true); - updateValue(e.target.value, false); + setFocusItem(); + setTmpValue(); + + const newValue = e.target.value; + onChange(newValue); + if (constricted) { + if (deselectOnClear && !newValue) setSelectedItem(); + } else if (newValue !== getItemValue(selectedItem)) { + setSelectedItem(); + } }, onBlur: ({ target }) => { @@ -126,14 +138,8 @@ const autocompleteLite = if (selectOnBlur && focusItem) { updateAll(focusItem); - } else if (constricted) { - if (value || !deselectOnBlur) updateAll(selectedItem); - else updateItem(); - } else if (getItemValue(selectedItem) != value) { - updateItem(); } - setTmpValue(); closeList(); }, @@ -156,15 +162,7 @@ const autocompleteLite = } break; case 'Escape': - if (open) { - if (constricted) { - updateAll(selectedItem); - } else if (!value || getItemValue(selectedItem) != value) { - updateItem(); - updateValue(value); - } - closeList(); - } + if (open) closeList(); break; } }, diff --git a/src/features/atom/dropdownToggle.ts b/src/features/atom/dropdownToggle.ts index bb5aa20..9f4fd9e 100644 --- a/src/features/atom/dropdownToggle.ts +++ b/src/features/atom/dropdownToggle.ts @@ -1,10 +1,12 @@ import { useEffect, useRef } from 'react'; -import type { Feature, GetPropsFunctions, GetPropsWithRefFunctions } from '../../common'; +import type { Feature, GetPropsFunctions, GetPropsWithRefFunctions, Clearable } from '../../common'; import { useMutableState } from '../../hooks/useMutableState'; type DropdownToggleFeature = Feature< T, - Pick, 'getToggleProps'> & Pick, 'getInputProps'> + Pick, 'getToggleProps'> & + Pick, 'getInputProps'> & + Clearable >; interface MutableState { @@ -17,22 +19,20 @@ interface MutableState { const dropdownToggle = (): DropdownToggleFeature => - ({ inputRef, open, setOpen, focusItem, onChange }) => { + ({ inputRef, open, setOpen, focusItem, value, tmpValue }) => { const mutable = useMutableState({}); const toggleRef = useRef(null); + const inputValue = tmpValue || value || ''; useEffect(() => { if (open) inputRef.current?.focus(); }, [open, inputRef]); - const openList = () => { - onChange(''); - setOpen(true); - }; - const focusToggle = () => setTimeout(() => toggleRef.current?.focus(), 0); return { + clearable: !!inputValue, + getToggleProps: () => ({ ref: toggleRef, @@ -44,7 +44,7 @@ const dropdownToggle = if (mutable.a) { mutable.a = 0; } else { - openList(); + setOpen(true); } }, @@ -52,12 +52,13 @@ const dropdownToggle = const { key } = e; if (key === 'ArrowDown') { e.preventDefault(); - openList(); + setOpen(true); } } }), getInputProps: () => ({ + value: inputValue, onKeyDown: (e) => { const { key } = e; if (key === 'Escape') focusToggle(); diff --git a/src/features/molecule/dropdown.ts b/src/features/molecule/dropdown.ts index 38fc800..88e1ae7 100644 --- a/src/features/molecule/dropdown.ts +++ b/src/features/molecule/dropdown.ts @@ -11,7 +11,7 @@ const dropdown = (props?: Pick, 'rovingText'>): DropdownFeatu ...props, constricted: true, selectOnBlur: false, - deselectOnBlur: false + deselectOnClear: false }), dropdownToggle() ); diff --git a/src/features/molecule/supercomplete.ts b/src/features/molecule/supercomplete.ts index 1452a8d..5d82fd9 100644 --- a/src/features/molecule/supercomplete.ts +++ b/src/features/molecule/supercomplete.ts @@ -10,7 +10,7 @@ const supercomplete = ({ ...rest }: Pick< FeatureProps, - 'getInlineItem' | 'constricted' | 'selectOnBlur' | 'deselectOnBlur' + 'getInlineItem' | 'constricted' | 'selectOnBlur' | 'deselectOnClear' >): SupercompleteFeature => mergeFeatures(autocomplete({ ...rest, rovingText: true }), inline({ getInlineItem })); diff --git a/src/hooks/useAutocomplete.ts b/src/hooks/useAutocomplete.ts index 189941e..e1a333b 100644 --- a/src/hooks/useAutocomplete.ts +++ b/src/hooks/useAutocomplete.ts @@ -2,8 +2,8 @@ import { useState, useRef, useCallback } from 'react'; import type { AutocompleteProps, AutocompleteState, Contextual } from '../common'; const useAutocomplete = ({ - value = '', - onChange = () => {}, + value, + onChange, isItemDisabled = () => false, feature: useFeature, traversal: useTraversal, @@ -35,7 +35,9 @@ const useAutocomplete = ({ getItemValue, isItemDisabled, value, - onChange, + onChange: (newValue?: string | undefined) => { + if (value != newValue) onChange?.(newValue); + }, inputRef, ...state }; diff --git a/types/common.d.ts b/types/common.d.ts index 1053d0e..d574cd5 100644 --- a/types/common.d.ts +++ b/types/common.d.ts @@ -24,8 +24,8 @@ export interface AutocompleteState { } export interface ContextualProps { isItemDisabled: (item: T) => boolean; - value: string; - onChange: (value: string) => void; + value: string | undefined; + onChange: (value?: string | undefined) => void; } export interface Contextual extends ContextualProps, AutocompleteState { tmpValue?: string; @@ -33,6 +33,9 @@ export interface Contextual extends ContextualProps, AutocompleteState inputRef: React.RefObject; getItemValue: (item: T | undefined | null) => string; } +export interface Clearable { + clearable: boolean; +} export interface TraversalProps { traverseInput?: boolean; } @@ -43,10 +46,10 @@ export interface FeatureProps { rovingText?: boolean; constricted?: boolean; selectOnBlur?: boolean; - deselectOnBlur?: boolean; + deselectOnClear?: boolean; getInlineItem: (value: string) => T | undefined | null | void | Promise; } -export type AutocompleteFeatureProps = Pick, 'rovingText' | 'constricted' | 'selectOnBlur' | 'deselectOnBlur'>; +export type AutocompleteFeatureProps = Pick, 'rovingText' | 'constricted' | 'selectOnBlur' | 'deselectOnClear'>; export type Feature = (cx: Contextual & ReturnType>) => Yield; export type MergedFeatureYield = Features extends readonly [Feature] ? S : Features extends readonly [Feature, ...infer R] ? F & MergedFeatureYield : never; export type MergedFeature = Feature>; diff --git a/types/features/atom/autocompleteLite.d.ts b/types/features/atom/autocompleteLite.d.ts index 805566a..96e0072 100644 --- a/types/features/atom/autocompleteLite.d.ts +++ b/types/features/atom/autocompleteLite.d.ts @@ -1,6 +1,4 @@ -import type { Feature, GetPropsFunctions, GetPropsWithRefFunctions, AutocompleteFeatureProps } from '../../common'; -type AutocompleteLiteFeature = Feature, 'getListProps' | 'getItemProps' | 'getClearProps'> & Pick, 'getInputProps'> & { - clearable: boolean; -}>; -declare const autocompleteLite: ({ rovingText, constricted, selectOnBlur, deselectOnBlur }?: AutocompleteFeatureProps) => AutocompleteLiteFeature; +import type { Feature, GetPropsFunctions, GetPropsWithRefFunctions, AutocompleteFeatureProps, Clearable } from '../../common'; +type AutocompleteLiteFeature = Feature, 'getListProps' | 'getItemProps' | 'getClearProps'> & Pick, 'getInputProps'> & Clearable>; +declare const autocompleteLite: ({ rovingText, constricted, selectOnBlur, deselectOnClear }?: AutocompleteFeatureProps) => AutocompleteLiteFeature; export { type AutocompleteLiteFeature, autocompleteLite }; diff --git a/types/features/atom/dropdownToggle.d.ts b/types/features/atom/dropdownToggle.d.ts index 9ccfa10..b924e9f 100644 --- a/types/features/atom/dropdownToggle.d.ts +++ b/types/features/atom/dropdownToggle.d.ts @@ -1,4 +1,4 @@ -import type { Feature, GetPropsFunctions, GetPropsWithRefFunctions } from '../../common'; -type DropdownToggleFeature = Feature, 'getToggleProps'> & Pick, 'getInputProps'>>; +import type { Feature, GetPropsFunctions, GetPropsWithRefFunctions, Clearable } from '../../common'; +type DropdownToggleFeature = Feature, 'getToggleProps'> & Pick, 'getInputProps'> & Clearable>; declare const dropdownToggle: () => DropdownToggleFeature; export { type DropdownToggleFeature, dropdownToggle }; diff --git a/types/features/molecule/supercomplete.d.ts b/types/features/molecule/supercomplete.d.ts index 89e08f4..2d86b70 100644 --- a/types/features/molecule/supercomplete.d.ts +++ b/types/features/molecule/supercomplete.d.ts @@ -2,5 +2,5 @@ import type { MergedFeature, FeatureProps } from '../../common'; import { type AutocompleteFeature } from './autocomplete'; import { type InlineFeature } from '../atom/inline'; type SupercompleteFeature = MergedFeature, InlineFeature]>; -declare const supercomplete: ({ getInlineItem, ...rest }: Pick, "constricted" | "selectOnBlur" | "deselectOnBlur" | "getInlineItem">) => SupercompleteFeature; +declare const supercomplete: ({ getInlineItem, ...rest }: Pick, "constricted" | "selectOnBlur" | "deselectOnClear" | "getInlineItem">) => SupercompleteFeature; export { type SupercompleteFeature, supercomplete }; From 9642313c3aa6a65f8b24cb5944e71b0f7dddf783 Mon Sep 17 00:00:00 2001 From: Zheng Song Date: Fri, 21 Jun 2024 01:40:49 +1000 Subject: [PATCH 2/8] Simply deselection in search mode * --- dist/cjs/index.js | 2 +- dist/esm/features/atom/autocompleteLite.js | 2 +- src/features/atom/autocompleteLite.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dist/cjs/index.js b/dist/cjs/index.js index 9ed9916..5e50762 100644 --- a/dist/cjs/index.js +++ b/dist/cjs/index.js @@ -168,7 +168,7 @@ const autocompleteLite = ({ onChange(newValue); if (constricted) { if (deselectOnClear && !newValue) setSelectedItem(); - } else if (newValue !== getItemValue(selectedItem)) { + } else { setSelectedItem(); } }, diff --git a/dist/esm/features/atom/autocompleteLite.js b/dist/esm/features/atom/autocompleteLite.js index ca6287f..9a33d2a 100644 --- a/dist/esm/features/atom/autocompleteLite.js +++ b/dist/esm/features/atom/autocompleteLite.js @@ -87,7 +87,7 @@ const autocompleteLite = ({ onChange(newValue); if (constricted) { if (deselectOnClear && !newValue) setSelectedItem(); - } else if (newValue !== getItemValue(selectedItem)) { + } else { setSelectedItem(); } }, diff --git a/src/features/atom/autocompleteLite.ts b/src/features/atom/autocompleteLite.ts index c268550..37a05c0 100644 --- a/src/features/atom/autocompleteLite.ts +++ b/src/features/atom/autocompleteLite.ts @@ -122,7 +122,7 @@ const autocompleteLite = onChange(newValue); if (constricted) { if (deselectOnClear && !newValue) setSelectedItem(); - } else if (newValue !== getItemValue(selectedItem)) { + } else { setSelectedItem(); } }, From f967899c9c2b94bb909252e22d76ee7092cce155 Mon Sep 17 00:00:00 2001 From: Zheng Song Date: Fri, 21 Jun 2024 23:36:19 +1000 Subject: [PATCH 3/8] Add `deselectOnChange` prop --- dist/cjs/index.js | 14 +++++--------- dist/esm/features/atom/autocompleteLite.js | 13 +++++-------- dist/esm/features/molecule/dropdown.js | 1 - examples/data.ts | 4 ++++ examples/pages/index.tsx | 2 +- src/common.ts | 3 ++- src/features/atom/autocompleteLite.ts | 13 +++++-------- src/features/molecule/dropdown.ts | 5 +++-- src/features/molecule/supercomplete.ts | 2 +- types/common.d.ts | 3 ++- types/features/atom/autocompleteLite.d.ts | 2 +- types/features/molecule/dropdown.d.ts | 2 +- types/features/molecule/supercomplete.d.ts | 2 +- 13 files changed, 31 insertions(+), 35 deletions(-) diff --git a/dist/cjs/index.js b/dist/cjs/index.js index 5e50762..2446fe0 100644 --- a/dist/cjs/index.js +++ b/dist/cjs/index.js @@ -87,8 +87,9 @@ const scrollIntoView = element => element == null ? void 0 : element.scrollIntoV const autocompleteLite = ({ rovingText, constricted, - selectOnBlur = true, - deselectOnClear = true + selectOnBlur = rovingText, + deselectOnClear = true, + deselectOnChange = true } = {}) => ({ getItemValue, isItemDisabled, @@ -138,7 +139,7 @@ const autocompleteLite = ({ onChange(''); setTmpValue(); setFocusItem(); - if (deselectOnClear) setSelectedItem(); + if (deselectOnClear) updateItem(); } }), getListProps: () => ({ @@ -166,11 +167,7 @@ const autocompleteLite = ({ setTmpValue(); const newValue = e.target.value; onChange(newValue); - if (constricted) { - if (deselectOnClear && !newValue) setSelectedItem(); - } else { - setSelectedItem(); - } + if (!constricted && deselectOnChange || deselectOnClear && !newValue) updateItem(); }, onBlur: ({ target @@ -337,7 +334,6 @@ const dropdownToggle = () => ({ const dropdown = props => mergeFeatures(autocompleteLite({ ...props, constricted: true, - selectOnBlur: false, deselectOnClear: false }), dropdownToggle()); diff --git a/dist/esm/features/atom/autocompleteLite.js b/dist/esm/features/atom/autocompleteLite.js index 9a33d2a..478ed17 100644 --- a/dist/esm/features/atom/autocompleteLite.js +++ b/dist/esm/features/atom/autocompleteLite.js @@ -6,8 +6,9 @@ const scrollIntoView = element => element == null ? void 0 : element.scrollIntoV const autocompleteLite = ({ rovingText, constricted, - selectOnBlur = true, - deselectOnClear = true + selectOnBlur = rovingText, + deselectOnClear = true, + deselectOnChange = true } = {}) => ({ getItemValue, isItemDisabled, @@ -57,7 +58,7 @@ const autocompleteLite = ({ onChange(''); setTmpValue(); setFocusItem(); - if (deselectOnClear) setSelectedItem(); + if (deselectOnClear) updateItem(); } }), getListProps: () => ({ @@ -85,11 +86,7 @@ const autocompleteLite = ({ setTmpValue(); const newValue = e.target.value; onChange(newValue); - if (constricted) { - if (deselectOnClear && !newValue) setSelectedItem(); - } else { - setSelectedItem(); - } + if (!constricted && deselectOnChange || deselectOnClear && !newValue) updateItem(); }, onBlur: ({ target diff --git a/dist/esm/features/molecule/dropdown.js b/dist/esm/features/molecule/dropdown.js index a97943d..ec46d5b 100644 --- a/dist/esm/features/molecule/dropdown.js +++ b/dist/esm/features/molecule/dropdown.js @@ -5,7 +5,6 @@ import { dropdownToggle } from '../atom/dropdownToggle.js'; const dropdown = props => mergeFeatures(autocompleteLite({ ...props, constricted: true, - selectOnBlur: false, deselectOnClear: false }), dropdownToggle()); diff --git a/examples/data.ts b/examples/data.ts index 56e6c74..0f55301 100644 --- a/examples/data.ts +++ b/examples/data.ts @@ -579,6 +579,10 @@ const LIST_GROUP = [ name: 'Alaska', abbr: 'AK' }, + { + name: 'a', + abbr: 'A' + }, { name: 'American Samoa', abbr: 'AS' diff --git a/examples/pages/index.tsx b/examples/pages/index.tsx index 7eb8955..b47b695 100644 --- a/examples/pages/index.tsx +++ b/examples/pages/index.tsx @@ -55,7 +55,7 @@ export default function Home() { isItemDisabled, value, onChange: (value) => { - console.log('onChange', value); + // console.log('onChange', value); setValue(value); }, // feature: autocomplete({ constricted, rovingText, selectOnBlur, deselectOnClear }), diff --git a/src/common.ts b/src/common.ts index 4b7b9dc..8af8ccb 100644 --- a/src/common.ts +++ b/src/common.ts @@ -59,6 +59,7 @@ export interface FeatureProps { constricted?: boolean; selectOnBlur?: boolean; deselectOnClear?: boolean; + deselectOnChange?: boolean; getInlineItem: ( value: string ) => T | undefined | null | void | Promise; @@ -66,7 +67,7 @@ export interface FeatureProps { export type AutocompleteFeatureProps = Pick< FeatureProps, - 'rovingText' | 'constricted' | 'selectOnBlur' | 'deselectOnClear' + 'rovingText' | 'constricted' | 'selectOnBlur' | 'deselectOnClear' | 'deselectOnChange' >; export type Feature = ( diff --git a/src/features/atom/autocompleteLite.ts b/src/features/atom/autocompleteLite.ts index 37a05c0..53c3e13 100644 --- a/src/features/atom/autocompleteLite.ts +++ b/src/features/atom/autocompleteLite.ts @@ -29,8 +29,9 @@ const autocompleteLite = ({ rovingText, constricted, - selectOnBlur = true, - deselectOnClear = true + selectOnBlur = rovingText, + deselectOnClear = true, + deselectOnChange = true }: AutocompleteFeatureProps = {}): AutocompleteLiteFeature => ({ getItemValue, @@ -88,7 +89,7 @@ const autocompleteLite = onChange(''); setTmpValue(); setFocusItem(); - if (deselectOnClear) setSelectedItem(); + if (deselectOnClear) updateItem(); } }), @@ -120,11 +121,7 @@ const autocompleteLite = const newValue = e.target.value; onChange(newValue); - if (constricted) { - if (deselectOnClear && !newValue) setSelectedItem(); - } else { - setSelectedItem(); - } + if ((!constricted && deselectOnChange) || (deselectOnClear && !newValue)) updateItem(); }, onBlur: ({ target }) => { diff --git a/src/features/molecule/dropdown.ts b/src/features/molecule/dropdown.ts index 88e1ae7..a430cb4 100644 --- a/src/features/molecule/dropdown.ts +++ b/src/features/molecule/dropdown.ts @@ -5,12 +5,13 @@ import { type DropdownToggleFeature, dropdownToggle } from '../atom/dropdownTogg type DropdownFeature = MergedFeature, DropdownToggleFeature]>; -const dropdown = (props?: Pick, 'rovingText'>): DropdownFeature => +const dropdown = ( + props?: Pick, 'rovingText' | 'selectOnBlur'> +): DropdownFeature => mergeFeatures( autocompleteLite({ ...props, constricted: true, - selectOnBlur: false, deselectOnClear: false }), dropdownToggle() diff --git a/src/features/molecule/supercomplete.ts b/src/features/molecule/supercomplete.ts index 5d82fd9..35c3349 100644 --- a/src/features/molecule/supercomplete.ts +++ b/src/features/molecule/supercomplete.ts @@ -10,7 +10,7 @@ const supercomplete = ({ ...rest }: Pick< FeatureProps, - 'getInlineItem' | 'constricted' | 'selectOnBlur' | 'deselectOnClear' + 'getInlineItem' | 'constricted' | 'selectOnBlur' | 'deselectOnClear' | 'deselectOnChange' >): SupercompleteFeature => mergeFeatures(autocomplete({ ...rest, rovingText: true }), inline({ getInlineItem })); diff --git a/types/common.d.ts b/types/common.d.ts index d574cd5..aaa9645 100644 --- a/types/common.d.ts +++ b/types/common.d.ts @@ -47,9 +47,10 @@ export interface FeatureProps { constricted?: boolean; selectOnBlur?: boolean; deselectOnClear?: boolean; + deselectOnChange?: boolean; getInlineItem: (value: string) => T | undefined | null | void | Promise; } -export type AutocompleteFeatureProps = Pick, 'rovingText' | 'constricted' | 'selectOnBlur' | 'deselectOnClear'>; +export type AutocompleteFeatureProps = Pick, 'rovingText' | 'constricted' | 'selectOnBlur' | 'deselectOnClear' | 'deselectOnChange'>; export type Feature = (cx: Contextual & ReturnType>) => Yield; export type MergedFeatureYield = Features extends readonly [Feature] ? S : Features extends readonly [Feature, ...infer R] ? F & MergedFeatureYield : never; export type MergedFeature = Feature>; diff --git a/types/features/atom/autocompleteLite.d.ts b/types/features/atom/autocompleteLite.d.ts index 96e0072..4ed0022 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>; -declare const autocompleteLite: ({ rovingText, constricted, selectOnBlur, deselectOnClear }?: AutocompleteFeatureProps) => AutocompleteLiteFeature; +declare const autocompleteLite: ({ rovingText, constricted, selectOnBlur, deselectOnClear, deselectOnChange }?: AutocompleteFeatureProps) => AutocompleteLiteFeature; export { type AutocompleteLiteFeature, autocompleteLite }; diff --git a/types/features/molecule/dropdown.d.ts b/types/features/molecule/dropdown.d.ts index 3dc3f5f..074b368 100644 --- a/types/features/molecule/dropdown.d.ts +++ b/types/features/molecule/dropdown.d.ts @@ -2,5 +2,5 @@ import type { MergedFeature, FeatureProps } from '../../common'; import { type AutocompleteLiteFeature } from '../atom/autocompleteLite'; import { type DropdownToggleFeature } from '../atom/dropdownToggle'; type DropdownFeature = MergedFeature, DropdownToggleFeature]>; -declare const dropdown: (props?: Pick, "rovingText"> | undefined) => DropdownFeature; +declare const dropdown: (props?: Pick, "rovingText" | "selectOnBlur"> | undefined) => DropdownFeature; export { type DropdownFeature, dropdown }; diff --git a/types/features/molecule/supercomplete.d.ts b/types/features/molecule/supercomplete.d.ts index 2d86b70..b5fed8b 100644 --- a/types/features/molecule/supercomplete.d.ts +++ b/types/features/molecule/supercomplete.d.ts @@ -2,5 +2,5 @@ import type { MergedFeature, FeatureProps } from '../../common'; import { type AutocompleteFeature } from './autocomplete'; import { type InlineFeature } from '../atom/inline'; type SupercompleteFeature = MergedFeature, InlineFeature]>; -declare const supercomplete: ({ getInlineItem, ...rest }: Pick, "constricted" | "selectOnBlur" | "deselectOnClear" | "getInlineItem">) => SupercompleteFeature; +declare const supercomplete: ({ getInlineItem, ...rest }: Pick, "getInlineItem" | "constricted" | "selectOnBlur" | "deselectOnClear" | "deselectOnChange">) => SupercompleteFeature; export { type SupercompleteFeature, supercomplete }; From 8faff4b8fa1606dc9c47691cd8db57f0a38e6cf2 Mon Sep 17 00:00:00 2001 From: Zheng Song Date: Sat, 22 Jun 2024 15:23:47 +1000 Subject: [PATCH 4/8] deselectOnChange example --- examples/pages/index.tsx | 71 +++++++++++++++++++++++++++------------- 1 file changed, 49 insertions(+), 22 deletions(-) diff --git a/examples/pages/index.tsx b/examples/pages/index.tsx index b47b695..3641723 100644 --- a/examples/pages/index.tsx +++ b/examples/pages/index.tsx @@ -20,10 +20,12 @@ const getGroupedItems = (value: string) => })).filter((group) => !!group.states.length); export default function Home() { + const [isSupercomplete, setSupercomplete] = useState(true); const [constricted, setConstricted] = useState(false); const [rovingText, setRovingText] = useState(true); const [selectOnBlur, setSelectOnBlur] = useState(true); - const [deselectOnClear, setDeselectOnClear] = useState(false); + const [deselectOnClear, setDeselectOnClear] = useState(true); + const [deselectOnChange, setDeselectOnChange] = useState(true); const [value, setValue] = useState(); const [anotherValue, setAnotherValue] = useState(''); const anotherRef = useRef(null); @@ -58,21 +60,24 @@ export default function Home() { // console.log('onChange', value); setValue(value); }, - // feature: autocomplete({ constricted, rovingText, selectOnBlur, deselectOnClear }), - feature: supercomplete({ - constricted, - selectOnBlur, - deselectOnClear, - getInlineItem: (newValue) => - getGroupedItems(newValue)[0]?.states.find((item) => !isItemDisabled(item)) - // getInlineItem: (newValue) => - // new Promise((res) => - // setTimeout( - // () => res(getGroupedItems(newValue)[0]?.states.find((item) => !isItemDisabled(item))), - // 1000 - // ) - // ) - }), + + feature: isSupercomplete + ? supercomplete({ + constricted, + selectOnBlur, + deselectOnClear, + deselectOnChange, + getInlineItem: (newValue) => + getGroupedItems(newValue)[0]?.states.find((item) => !isItemDisabled(item)) + // getInlineItem: (newValue) => + // new Promise((res) => + // setTimeout( + // () => res(getGroupedItems(newValue)[0]?.states.find((item) => !isItemDisabled(item))), + // 1000 + // ) + // ) + }) + : autocomplete({ constricted, selectOnBlur, deselectOnClear, deselectOnChange, rovingText }), traversal: groupedTraversal({ traverseInput: true, groupedItems, @@ -89,24 +94,36 @@ export default function Home() {
Focus item: {focusItem?.name}
+ {!isSupercomplete && ( +
+ +
+ )}
+
+ +
Date: Sat, 22 Jun 2024 16:02:31 +1000 Subject: [PATCH 5/8] Rename `constricted` to `select` prop --- dist/cjs/index.js | 10 +++++----- dist/esm/features/atom/autocompleteLite.js | 8 ++++---- dist/esm/features/molecule/dropdown.js | 2 +- examples/pages/dropdownExample.tsx | 2 +- examples/pages/index.tsx | 14 +++++--------- src/common.ts | 4 ++-- src/features/atom/autocompleteLite.ts | 8 ++++---- src/features/molecule/dropdown.ts | 2 +- src/features/molecule/supercomplete.ts | 2 +- types/common.d.ts | 4 ++-- types/features/atom/autocompleteLite.d.ts | 2 +- types/features/molecule/supercomplete.d.ts | 2 +- 12 files changed, 28 insertions(+), 32 deletions(-) diff --git a/dist/cjs/index.js b/dist/cjs/index.js index 2446fe0..564c06f 100644 --- a/dist/cjs/index.js +++ b/dist/cjs/index.js @@ -86,7 +86,7 @@ const scrollIntoView = element => element == null ? void 0 : element.scrollIntoV }); const autocompleteLite = ({ rovingText, - constricted, + select, selectOnBlur = rovingText, deselectOnClear = true, deselectOnChange = true @@ -112,7 +112,7 @@ const autocompleteLite = ({ const updateValue = newValue => { const endIndex = newValue.length; inputRef.current.setSelectionRange(endIndex, endIndex); - if (!constricted) onChange(newValue); + if (!select) onChange(newValue); }; const updateItem = item => item !== selectedItem && setSelectedItem(item); const updateAll = item => { @@ -123,7 +123,7 @@ const autocompleteLite = ({ setOpen(false); setFocusItem(); setTmpValue(); - if (constricted) onChange(); + if (select) onChange(); }; return { clearable: !!inputValue, @@ -167,7 +167,7 @@ const autocompleteLite = ({ setTmpValue(); const newValue = e.target.value; onChange(newValue); - if (!constricted && deselectOnChange || deselectOnClear && !newValue) updateItem(); + if (!select && deselectOnChange || deselectOnClear && !newValue) updateItem(); }, onBlur: ({ target @@ -333,7 +333,7 @@ const dropdownToggle = () => ({ const dropdown = props => mergeFeatures(autocompleteLite({ ...props, - constricted: true, + select: true, deselectOnClear: false }), dropdownToggle()); diff --git a/dist/esm/features/atom/autocompleteLite.js b/dist/esm/features/atom/autocompleteLite.js index 478ed17..18fd04f 100644 --- a/dist/esm/features/atom/autocompleteLite.js +++ b/dist/esm/features/atom/autocompleteLite.js @@ -5,7 +5,7 @@ const scrollIntoView = element => element == null ? void 0 : element.scrollIntoV }); const autocompleteLite = ({ rovingText, - constricted, + select, selectOnBlur = rovingText, deselectOnClear = true, deselectOnChange = true @@ -31,7 +31,7 @@ const autocompleteLite = ({ const updateValue = newValue => { const endIndex = newValue.length; inputRef.current.setSelectionRange(endIndex, endIndex); - if (!constricted) onChange(newValue); + if (!select) onChange(newValue); }; const updateItem = item => item !== selectedItem && setSelectedItem(item); const updateAll = item => { @@ -42,7 +42,7 @@ const autocompleteLite = ({ setOpen(false); setFocusItem(); setTmpValue(); - if (constricted) onChange(); + if (select) onChange(); }; return { clearable: !!inputValue, @@ -86,7 +86,7 @@ const autocompleteLite = ({ setTmpValue(); const newValue = e.target.value; onChange(newValue); - if (!constricted && deselectOnChange || deselectOnClear && !newValue) updateItem(); + if (!select && deselectOnChange || deselectOnClear && !newValue) updateItem(); }, onBlur: ({ target diff --git a/dist/esm/features/molecule/dropdown.js b/dist/esm/features/molecule/dropdown.js index ec46d5b..2707303 100644 --- a/dist/esm/features/molecule/dropdown.js +++ b/dist/esm/features/molecule/dropdown.js @@ -4,7 +4,7 @@ import { dropdownToggle } from '../atom/dropdownToggle.js'; const dropdown = props => mergeFeatures(autocompleteLite({ ...props, - constricted: true, + select: true, deselectOnClear: false }), dropdownToggle()); diff --git a/examples/pages/dropdownExample.tsx b/examples/pages/dropdownExample.tsx index 5fb10b0..0fe8f84 100644 --- a/examples/pages/dropdownExample.tsx +++ b/examples/pages/dropdownExample.tsx @@ -109,7 +109,7 @@ export default function Dropdown() { }} >
- + {clearable && (
{!isSupercomplete && ( diff --git a/src/common.ts b/src/common.ts index 8af8ccb..57aec05 100644 --- a/src/common.ts +++ b/src/common.ts @@ -56,7 +56,7 @@ export type Traversal = (cx: Contextual) => { export interface FeatureProps { rovingText?: boolean; - constricted?: boolean; + select?: boolean; selectOnBlur?: boolean; deselectOnClear?: boolean; deselectOnChange?: boolean; @@ -67,7 +67,7 @@ export interface FeatureProps { export type AutocompleteFeatureProps = Pick< FeatureProps, - 'rovingText' | 'constricted' | 'selectOnBlur' | 'deselectOnClear' | 'deselectOnChange' + 'rovingText' | 'select' | 'selectOnBlur' | 'deselectOnClear' | 'deselectOnChange' >; export type Feature = ( diff --git a/src/features/atom/autocompleteLite.ts b/src/features/atom/autocompleteLite.ts index 53c3e13..0db687a 100644 --- a/src/features/atom/autocompleteLite.ts +++ b/src/features/atom/autocompleteLite.ts @@ -28,7 +28,7 @@ const scrollIntoView = (element: HTMLElement | null) => const autocompleteLite = ({ rovingText, - constricted, + select, selectOnBlur = rovingText, deselectOnClear = true, deselectOnChange = true @@ -56,7 +56,7 @@ const autocompleteLite = const updateValue = (newValue: string) => { const endIndex = newValue.length; inputRef.current!.setSelectionRange(endIndex, endIndex); - if (!constricted) onChange(newValue); + if (!select) onChange(newValue); }; const updateItem = (item?: T) => item !== selectedItem && setSelectedItem(item); @@ -70,7 +70,7 @@ const autocompleteLite = setOpen(false); setFocusItem(); setTmpValue(); - if (constricted) onChange(); + if (select) onChange(); }; return { @@ -121,7 +121,7 @@ const autocompleteLite = const newValue = e.target.value; onChange(newValue); - if ((!constricted && deselectOnChange) || (deselectOnClear && !newValue)) updateItem(); + if ((!select && deselectOnChange) || (deselectOnClear && !newValue)) updateItem(); }, onBlur: ({ target }) => { diff --git a/src/features/molecule/dropdown.ts b/src/features/molecule/dropdown.ts index a430cb4..bccd8e9 100644 --- a/src/features/molecule/dropdown.ts +++ b/src/features/molecule/dropdown.ts @@ -11,7 +11,7 @@ const dropdown = ( mergeFeatures( autocompleteLite({ ...props, - constricted: true, + select: true, deselectOnClear: false }), dropdownToggle() diff --git a/src/features/molecule/supercomplete.ts b/src/features/molecule/supercomplete.ts index 35c3349..24bc59b 100644 --- a/src/features/molecule/supercomplete.ts +++ b/src/features/molecule/supercomplete.ts @@ -10,7 +10,7 @@ const supercomplete = ({ ...rest }: Pick< FeatureProps, - 'getInlineItem' | 'constricted' | 'selectOnBlur' | 'deselectOnClear' | 'deselectOnChange' + 'getInlineItem' | 'select' | 'selectOnBlur' | 'deselectOnClear' | 'deselectOnChange' >): SupercompleteFeature => mergeFeatures(autocomplete({ ...rest, rovingText: true }), inline({ getInlineItem })); diff --git a/types/common.d.ts b/types/common.d.ts index aaa9645..0a3b8d7 100644 --- a/types/common.d.ts +++ b/types/common.d.ts @@ -44,13 +44,13 @@ export type Traversal = (cx: Contextual) => { }; export interface FeatureProps { rovingText?: boolean; - constricted?: boolean; + select?: boolean; selectOnBlur?: boolean; deselectOnClear?: boolean; deselectOnChange?: boolean; getInlineItem: (value: string) => T | undefined | null | void | Promise; } -export type AutocompleteFeatureProps = Pick, 'rovingText' | 'constricted' | 'selectOnBlur' | 'deselectOnClear' | 'deselectOnChange'>; +export type AutocompleteFeatureProps = Pick, 'rovingText' | 'select' | 'selectOnBlur' | 'deselectOnClear' | 'deselectOnChange'>; export type Feature = (cx: Contextual & ReturnType>) => Yield; export type MergedFeatureYield = Features extends readonly [Feature] ? S : Features extends readonly [Feature, ...infer R] ? F & MergedFeatureYield : never; export type MergedFeature = Feature>; diff --git a/types/features/atom/autocompleteLite.d.ts b/types/features/atom/autocompleteLite.d.ts index 4ed0022..6c511aa 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>; -declare const autocompleteLite: ({ rovingText, constricted, selectOnBlur, deselectOnClear, deselectOnChange }?: AutocompleteFeatureProps) => AutocompleteLiteFeature; +declare const autocompleteLite: ({ rovingText, select, selectOnBlur, deselectOnClear, deselectOnChange }?: AutocompleteFeatureProps) => AutocompleteLiteFeature; export { type AutocompleteLiteFeature, autocompleteLite }; diff --git a/types/features/molecule/supercomplete.d.ts b/types/features/molecule/supercomplete.d.ts index b5fed8b..b71c9e8 100644 --- a/types/features/molecule/supercomplete.d.ts +++ b/types/features/molecule/supercomplete.d.ts @@ -2,5 +2,5 @@ import type { MergedFeature, FeatureProps } from '../../common'; import { type AutocompleteFeature } from './autocomplete'; import { type InlineFeature } from '../atom/inline'; type SupercompleteFeature = MergedFeature, InlineFeature]>; -declare const supercomplete: ({ getInlineItem, ...rest }: Pick, "getInlineItem" | "constricted" | "selectOnBlur" | "deselectOnClear" | "deselectOnChange">) => SupercompleteFeature; +declare const supercomplete: ({ getInlineItem, ...rest }: Pick, "select" | "selectOnBlur" | "deselectOnClear" | "deselectOnChange" | "getInlineItem">) => SupercompleteFeature; export { type SupercompleteFeature, supercomplete }; From d131d005c77c0faddf535b1a81a985ad8c5f9214 Mon Sep 17 00:00:00 2001 From: Zheng Song Date: Sat, 22 Jun 2024 16:31:50 +1000 Subject: [PATCH 6/8] Remove useCallback for getItemValue --- dist/cjs/index.js | 2 +- dist/esm/hooks/useAutocomplete.js | 4 ++-- src/hooks/useAutocomplete.ts | 8 +++----- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/dist/cjs/index.js b/dist/cjs/index.js index 564c06f..a938c98 100644 --- a/dist/cjs/index.js +++ b/dist/cjs/index.js @@ -15,7 +15,7 @@ const useAutocomplete = ({ const [open, setOpen] = react.useState(false); const [focusItem, setFocusItem] = react.useState(); const [selectedItem, setSelectedItem] = react.useState(); - const getItemValue = react.useCallback(item => item == null ? '' : _getItemValue ? _getItemValue(item) : item.toString(), [_getItemValue]); + const getItemValue = item => item == null ? '' : _getItemValue ? _getItemValue(item) : item.toString(); const state = { focusItem, setFocusItem, diff --git a/dist/esm/hooks/useAutocomplete.js b/dist/esm/hooks/useAutocomplete.js index 05d340e..0e2a58f 100644 --- a/dist/esm/hooks/useAutocomplete.js +++ b/dist/esm/hooks/useAutocomplete.js @@ -1,4 +1,4 @@ -import { useRef, useState, useCallback } from 'react'; +import { useRef, useState } from 'react'; const useAutocomplete = ({ value, @@ -13,7 +13,7 @@ const useAutocomplete = ({ const [open, setOpen] = useState(false); const [focusItem, setFocusItem] = useState(); const [selectedItem, setSelectedItem] = useState(); - const getItemValue = useCallback(item => item == null ? '' : _getItemValue ? _getItemValue(item) : item.toString(), [_getItemValue]); + const getItemValue = item => item == null ? '' : _getItemValue ? _getItemValue(item) : item.toString(); const state = { focusItem, setFocusItem, diff --git a/src/hooks/useAutocomplete.ts b/src/hooks/useAutocomplete.ts index e1a333b..ee0f6a5 100644 --- a/src/hooks/useAutocomplete.ts +++ b/src/hooks/useAutocomplete.ts @@ -1,4 +1,4 @@ -import { useState, useRef, useCallback } from 'react'; +import { useState, useRef } from 'react'; import type { AutocompleteProps, AutocompleteState, Contextual } from '../common'; const useAutocomplete = ({ @@ -15,10 +15,8 @@ const useAutocomplete = ({ const [focusItem, setFocusItem] = useState(); const [selectedItem, setSelectedItem] = useState(); - const getItemValue: Contextual['getItemValue'] = useCallback( - (item) => (item == null ? '' : _getItemValue ? _getItemValue(item) : item.toString()), - [_getItemValue] - ); + const getItemValue: Contextual['getItemValue'] = (item) => + item == null ? '' : _getItemValue ? _getItemValue(item) : item.toString(); const state: AutocompleteState = { focusItem, From 418d6783bdee172500fc6fec0191290899ef08df Mon Sep 17 00:00:00 2001 From: Zheng Song Date: Sat, 22 Jun 2024 17:14:22 +1000 Subject: [PATCH 7/8] Make selected item controlled --- dist/cjs/index.js | 20 +++++++++----------- dist/esm/features/atom/autocompleteLite.js | 9 ++++----- dist/esm/hooks/useAutocomplete.js | 11 +++++------ examples/pages/dropdownExample.tsx | 21 ++++----------------- examples/pages/index.tsx | 11 ++++++++--- src/common.ts | 4 ++-- src/features/atom/autocompleteLite.ts | 11 +++++------ src/hooks/useAutocomplete.ts | 12 ++++++------ types/common.d.ts | 4 ++-- types/hooks/useAutocomplete.d.ts | 4 +--- 10 files changed, 46 insertions(+), 61 deletions(-) diff --git a/dist/cjs/index.js b/dist/cjs/index.js index a938c98..7d07b76 100644 --- a/dist/cjs/index.js +++ b/dist/cjs/index.js @@ -5,6 +5,8 @@ var react = require('react'); const useAutocomplete = ({ value, onChange, + selectedItem, + onSelectedItemChange, isItemDisabled = () => false, feature: useFeature, traversal: useTraversal, @@ -14,13 +16,10 @@ const useAutocomplete = ({ const [tmpValue, setTmpValue] = react.useState(); const [open, setOpen] = react.useState(false); const [focusItem, setFocusItem] = react.useState(); - const [selectedItem, setSelectedItem] = react.useState(); const getItemValue = item => item == null ? '' : _getItemValue ? _getItemValue(item) : item.toString(); const state = { focusItem, setFocusItem, - selectedItem, - setSelectedItem, open, setOpen }; @@ -30,9 +29,9 @@ const useAutocomplete = ({ getItemValue, isItemDisabled, value, - onChange: newValue => { - if (value != newValue) onChange == null || onChange(newValue); - }, + onChange: newValue => value != newValue && (onChange == null ? void 0 : onChange(newValue)), + selectedItem, + onSelectedItemChange: newItem => newItem !== selectedItem && (onSelectedItemChange == null ? void 0 : onSelectedItemChange(newItem)), inputRef, ...state }; @@ -99,7 +98,7 @@ const autocompleteLite = ({ tmpValue, setTmpValue, selectedItem, - setSelectedItem, + onSelectedItemChange, focusItem, setFocusItem, open, @@ -114,9 +113,8 @@ const autocompleteLite = ({ inputRef.current.setSelectionRange(endIndex, endIndex); if (!select) onChange(newValue); }; - const updateItem = item => item !== selectedItem && setSelectedItem(item); const updateAll = item => { - updateItem(item); + onSelectedItemChange(item); updateValue(getItemValue(item)); }; const closeList = () => { @@ -139,7 +137,7 @@ const autocompleteLite = ({ onChange(''); setTmpValue(); setFocusItem(); - if (deselectOnClear) updateItem(); + if (deselectOnClear) onSelectedItemChange(); } }), getListProps: () => ({ @@ -167,7 +165,7 @@ const autocompleteLite = ({ setTmpValue(); const newValue = e.target.value; onChange(newValue); - if (!select && deselectOnChange || deselectOnClear && !newValue) updateItem(); + if (!select && deselectOnChange || deselectOnClear && !newValue) onSelectedItemChange(); }, onBlur: ({ target diff --git a/dist/esm/features/atom/autocompleteLite.js b/dist/esm/features/atom/autocompleteLite.js index 18fd04f..f2d75ce 100644 --- a/dist/esm/features/atom/autocompleteLite.js +++ b/dist/esm/features/atom/autocompleteLite.js @@ -18,7 +18,7 @@ const autocompleteLite = ({ tmpValue, setTmpValue, selectedItem, - setSelectedItem, + onSelectedItemChange, focusItem, setFocusItem, open, @@ -33,9 +33,8 @@ const autocompleteLite = ({ inputRef.current.setSelectionRange(endIndex, endIndex); if (!select) onChange(newValue); }; - const updateItem = item => item !== selectedItem && setSelectedItem(item); const updateAll = item => { - updateItem(item); + onSelectedItemChange(item); updateValue(getItemValue(item)); }; const closeList = () => { @@ -58,7 +57,7 @@ const autocompleteLite = ({ onChange(''); setTmpValue(); setFocusItem(); - if (deselectOnClear) updateItem(); + if (deselectOnClear) onSelectedItemChange(); } }), getListProps: () => ({ @@ -86,7 +85,7 @@ const autocompleteLite = ({ setTmpValue(); const newValue = e.target.value; onChange(newValue); - if (!select && deselectOnChange || deselectOnClear && !newValue) updateItem(); + if (!select && deselectOnChange || deselectOnClear && !newValue) onSelectedItemChange(); }, onBlur: ({ target diff --git a/dist/esm/hooks/useAutocomplete.js b/dist/esm/hooks/useAutocomplete.js index 0e2a58f..2970345 100644 --- a/dist/esm/hooks/useAutocomplete.js +++ b/dist/esm/hooks/useAutocomplete.js @@ -3,6 +3,8 @@ import { useRef, useState } from 'react'; const useAutocomplete = ({ value, onChange, + selectedItem, + onSelectedItemChange, isItemDisabled = () => false, feature: useFeature, traversal: useTraversal, @@ -12,13 +14,10 @@ const useAutocomplete = ({ const [tmpValue, setTmpValue] = useState(); const [open, setOpen] = useState(false); const [focusItem, setFocusItem] = useState(); - const [selectedItem, setSelectedItem] = useState(); const getItemValue = item => item == null ? '' : _getItemValue ? _getItemValue(item) : item.toString(); const state = { focusItem, setFocusItem, - selectedItem, - setSelectedItem, open, setOpen }; @@ -28,9 +27,9 @@ const useAutocomplete = ({ getItemValue, isItemDisabled, value, - onChange: newValue => { - if (value != newValue) onChange == null || onChange(newValue); - }, + onChange: newValue => value != newValue && (onChange == null ? void 0 : onChange(newValue)), + selectedItem, + onSelectedItemChange: newItem => newItem !== selectedItem && (onSelectedItemChange == null ? void 0 : onSelectedItemChange(newItem)), inputRef, ...state }; diff --git a/examples/pages/dropdownExample.tsx b/examples/pages/dropdownExample.tsx index 0fe8f84..caad246 100644 --- a/examples/pages/dropdownExample.tsx +++ b/examples/pages/dropdownExample.tsx @@ -21,10 +21,7 @@ const getGroupedItems = (value: string = '') => export default function Dropdown() { const [rovingText, setRovingText] = useState(false); const [value, setValue] = useState(''); - // const items = US_STATES.filter((item) => item.name.toLowerCase().startsWith(value.toLowerCase())); - // const [myinput, setmyinput] = useState(''); - // const [items, setItems] = useState(US_STATES); - // const feature = supercomplete<{ name: string; abbr: string }>(); + const [selectedItem, setSelectedItem] = useState(); const groupedItems = getGroupedItems(value); @@ -36,8 +33,7 @@ export default function Dropdown() { getClearProps, clearable, open, - focusItem, - selectedItem + focusItem // inlineComplete } = useAutocomplete({ // traversal: linearTraversal({ @@ -57,6 +53,8 @@ export default function Dropdown() { // const item = getGroupedItems(value)[0]?.states.find((item) => !isItemDisabled(item)); // item && inlineComplete({ item }); }, + selectedItem, + onSelectedItemChange: setSelectedItem, // feature: autocomplete({ constricted, rovingText }), feature: dropdown({ rovingText }), traversal: groupedTraversal({ @@ -66,19 +64,8 @@ export default function Dropdown() { }) }); - // useEffect(() => { - // items.length && inlineComplete({ index: 0, value: items[0] }); - // }, [items, inlineComplete]); - const inputProps = getInputProps(); - // useEffect(() => { - // if (open) { - // console.log('inputProps.ref.current useEffect', inputProps.ref.current); - // inputProps.ref.current?.focus(); - // } - // }, [open]); - const [maxHeight] = useAutoHeight({ anchorRef: inputProps.ref, show: open, margin: 30 }); return ( diff --git a/examples/pages/index.tsx b/examples/pages/index.tsx index 8c39be6..df15f40 100644 --- a/examples/pages/index.tsx +++ b/examples/pages/index.tsx @@ -26,7 +26,10 @@ export default function Home() { const [selectOnBlur, setSelectOnBlur] = useState(true); const [deselectOnClear, setDeselectOnClear] = useState(true); const [deselectOnChange, setDeselectOnChange] = useState(true); + const [value, setValue] = useState(); + const [selectedItem, setSelectedItem] = useState(); + const [anotherValue, setAnotherValue] = useState(''); const anotherRef = useRef(null); // const items = US_STATES.filter((item) => item.name.toLowerCase().startsWith(value.toLowerCase())); @@ -43,10 +46,7 @@ export default function Home() { getToggleProps, getClearProps, open, - setOpen, focusItem, - selectedItem, - setSelectedItem, clearable } = useAutocomplete({ // traversal: linearTraversal({ @@ -60,6 +60,11 @@ export default function Home() { // console.log('onChange', value); setValue(value); }, + selectedItem, + onSelectedItemChange: (item) => { + // console.log('onSelectedItemChange', item); + setSelectedItem(item); + }, feature: isSupercomplete ? supercomplete({ diff --git a/src/common.ts b/src/common.ts index 57aec05..9947485 100644 --- a/src/common.ts +++ b/src/common.ts @@ -23,8 +23,6 @@ export type GetPropsWithRefFunctions = { export interface AutocompleteState { focusItem: T | undefined; setFocusItem: (item?: T | undefined) => void; - selectedItem: T | undefined; - setSelectedItem: (item?: T | undefined) => void; open: boolean; setOpen: (value: boolean) => void; } @@ -33,6 +31,8 @@ export interface ContextualProps { isItemDisabled: (item: T) => boolean; value: string | undefined; onChange: (value?: string | undefined) => void; + selectedItem: T | undefined; + onSelectedItemChange: (item?: T | undefined) => void; } export interface Contextual extends ContextualProps, AutocompleteState { diff --git a/src/features/atom/autocompleteLite.ts b/src/features/atom/autocompleteLite.ts index 0db687a..33e18a9 100644 --- a/src/features/atom/autocompleteLite.ts +++ b/src/features/atom/autocompleteLite.ts @@ -42,7 +42,7 @@ const autocompleteLite = tmpValue, setTmpValue, selectedItem, - setSelectedItem, + onSelectedItemChange, focusItem, setFocusItem, open, @@ -59,10 +59,8 @@ const autocompleteLite = if (!select) onChange(newValue); }; - const updateItem = (item?: T) => item !== selectedItem && setSelectedItem(item); - const updateAll = (item?: T) => { - updateItem(item); + onSelectedItemChange(item); updateValue(getItemValue(item)); }; @@ -89,7 +87,7 @@ const autocompleteLite = onChange(''); setTmpValue(); setFocusItem(); - if (deselectOnClear) updateItem(); + if (deselectOnClear) onSelectedItemChange(); } }), @@ -121,7 +119,8 @@ const autocompleteLite = const newValue = e.target.value; onChange(newValue); - if ((!select && deselectOnChange) || (deselectOnClear && !newValue)) updateItem(); + if ((!select && deselectOnChange) || (deselectOnClear && !newValue)) + onSelectedItemChange(); }, onBlur: ({ target }) => { diff --git a/src/hooks/useAutocomplete.ts b/src/hooks/useAutocomplete.ts index ee0f6a5..2679f7f 100644 --- a/src/hooks/useAutocomplete.ts +++ b/src/hooks/useAutocomplete.ts @@ -4,6 +4,8 @@ import type { AutocompleteProps, AutocompleteState, Contextual } from '../common const useAutocomplete = ({ value, onChange, + selectedItem, + onSelectedItemChange, isItemDisabled = () => false, feature: useFeature, traversal: useTraversal, @@ -13,7 +15,6 @@ const useAutocomplete = ({ const [tmpValue, setTmpValue] = useState(); const [open, setOpen] = useState(false); const [focusItem, setFocusItem] = useState(); - const [selectedItem, setSelectedItem] = useState(); const getItemValue: Contextual['getItemValue'] = (item) => item == null ? '' : _getItemValue ? _getItemValue(item) : item.toString(); @@ -21,8 +22,6 @@ const useAutocomplete = ({ const state: AutocompleteState = { focusItem, setFocusItem, - selectedItem, - setSelectedItem, open, setOpen }; @@ -33,9 +32,10 @@ const useAutocomplete = ({ getItemValue, isItemDisabled, value, - onChange: (newValue?: string | undefined) => { - if (value != newValue) onChange?.(newValue); - }, + onChange: (newValue?: string | undefined) => value != newValue && onChange?.(newValue), + selectedItem, + onSelectedItemChange: (newItem?: T | undefined) => + newItem !== selectedItem && onSelectedItemChange?.(newItem), inputRef, ...state }; diff --git a/types/common.d.ts b/types/common.d.ts index 0a3b8d7..d644c05 100644 --- a/types/common.d.ts +++ b/types/common.d.ts @@ -17,8 +17,6 @@ export type GetPropsWithRefFunctions = { export interface AutocompleteState { focusItem: T | undefined; setFocusItem: (item?: T | undefined) => void; - selectedItem: T | undefined; - setSelectedItem: (item?: T | undefined) => void; open: boolean; setOpen: (value: boolean) => void; } @@ -26,6 +24,8 @@ export interface ContextualProps { isItemDisabled: (item: T) => boolean; value: string | undefined; onChange: (value?: string | undefined) => void; + selectedItem: T | undefined; + onSelectedItemChange: (item?: T | undefined) => void; } export interface Contextual extends ContextualProps, AutocompleteState { tmpValue?: string; diff --git a/types/hooks/useAutocomplete.d.ts b/types/hooks/useAutocomplete.d.ts index 281f89e..29bd274 100644 --- a/types/hooks/useAutocomplete.d.ts +++ b/types/hooks/useAutocomplete.d.ts @@ -1,9 +1,7 @@ import type { AutocompleteProps } from '../common'; -declare const useAutocomplete: ({ value, onChange, isItemDisabled, feature: useFeature, traversal: useTraversal, getItemValue: _getItemValue }: AutocompleteProps) => { +declare const useAutocomplete: ({ value, onChange, selectedItem, onSelectedItemChange, isItemDisabled, feature: useFeature, traversal: useTraversal, getItemValue: _getItemValue }: AutocompleteProps) => { focusItem: T | undefined; setFocusItem: (item?: T | undefined) => void; - selectedItem: T | undefined; - setSelectedItem: (item?: T | undefined) => void; open: boolean; setOpen: (value: boolean) => void; } & FeatureYield; From f94dd6c29fe99c42f7c5f315a8ba314d5838e143 Mon Sep 17 00:00:00 2001 From: Zheng Song Date: Sat, 22 Jun 2024 17:17:28 +1000 Subject: [PATCH 8/8] 0.8.10 --- examples/package-lock.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/package-lock.json b/examples/package-lock.json index 7cd9825..cc60c44 100644 --- a/examples/package-lock.json +++ b/examples/package-lock.json @@ -24,7 +24,7 @@ }, "..": { "name": "@szhsin/react-autocomplete", - "version": "0.8.9", + "version": "0.8.10", "license": "MIT", "devDependencies": { "@babel/core": "^7.23.2", diff --git a/package-lock.json b/package-lock.json index 4e0d029..1782d64 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@szhsin/react-autocomplete", - "version": "0.8.9", + "version": "0.8.10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@szhsin/react-autocomplete", - "version": "0.8.9", + "version": "0.8.10", "license": "MIT", "devDependencies": { "@babel/core": "^7.23.2", diff --git a/package.json b/package.json index 445a860..ead2efd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@szhsin/react-autocomplete", - "version": "0.8.9", + "version": "0.8.10", "description": "", "author": "Zheng Song", "license": "MIT",