mirror of
https://github.com/fergalmoran/turnstone.git
synced 2026-01-04 16:15:39 +00:00
Add useHighlight hook
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
|||||||
useItemsState,
|
useItemsState,
|
||||||
useAutoFocus,
|
useAutoFocus,
|
||||||
useQueryChange,
|
useQueryChange,
|
||||||
|
useHighlight
|
||||||
} from './hooks/containerEffects'
|
} from './hooks/containerEffects'
|
||||||
import {
|
import {
|
||||||
setQuery,
|
setQuery,
|
||||||
@@ -95,25 +96,12 @@ export default function Container(props) {
|
|||||||
useAutoFocus(queryInput, autoFocus)
|
useAutoFocus(queryInput, autoFocus)
|
||||||
|
|
||||||
// As soon as the query state changes (ignoring debounce) update the
|
// As soon as the query state changes (ignoring debounce) update the
|
||||||
// typeahead value and the query value and fire onChnage
|
// typeahead value and the query value and fire onChange
|
||||||
useQueryChange(state.query, queryInput, typeaheadInput, onChange)
|
useQueryChange(state.query, queryInput, typeaheadInput, onChange)
|
||||||
|
|
||||||
// When the highlighted item changes, make sure the typeahead matches and format
|
// When the highlighted item changes, make sure the typeahead matches and format
|
||||||
// the query text to match the case of the typeahead text
|
// the query text to match the case of the typeahead text
|
||||||
useEffect(() => {
|
useHighlight(state.highlighted, hasFocus, queryInput, typeaheadInput)
|
||||||
const typeAheadValue =
|
|
||||||
state.highlighted &&
|
|
||||||
hasFocus &&
|
|
||||||
queryInput.current.value.length > 0 &&
|
|
||||||
startsWithCaseInsensitive(state.highlighted.text, queryInput.current.value)
|
|
||||||
? state.highlighted.text
|
|
||||||
: ''
|
|
||||||
const queryValue = formatQuery(queryInput.current.value, typeAheadValue)
|
|
||||||
|
|
||||||
typeaheadInput.current.value = typeAheadValue
|
|
||||||
|
|
||||||
setify(queryInput.current, queryValue)
|
|
||||||
}, [state.highlighted, hasFocus])
|
|
||||||
|
|
||||||
// When an item is selected alter the query to match and fire applicable events
|
// When an item is selected alter the query to match and fire applicable events
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -125,15 +113,6 @@ export default function Container(props) {
|
|||||||
}
|
}
|
||||||
}, [state.selected, onSelect])
|
}, [state.selected, onSelect])
|
||||||
|
|
||||||
const formatQuery = (query, typeahead) => {
|
|
||||||
const formattedQuery = typeahead.substring(0, query.length)
|
|
||||||
return formattedQuery.length > 0 &&
|
|
||||||
query.toLowerCase() === formattedQuery.toLowerCase() &&
|
|
||||||
query !== formattedQuery
|
|
||||||
? formattedQuery
|
|
||||||
: query
|
|
||||||
}
|
|
||||||
|
|
||||||
const onTabOrEnter = (keyPressed) => {
|
const onTabOrEnter = (keyPressed) => {
|
||||||
// keyPressed must be 'enter' or 'tab'
|
// keyPressed must be 'enter' or 'tab'
|
||||||
const highlightedIndex = state.highlighted && state.highlighted.index
|
const highlightedIndex = state.highlighted && state.highlighted.index
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useEffect, useContext } from 'react'
|
|||||||
import setify from 'setify' // Sets input value without changing cursor position
|
import setify from 'setify' // Sets input value without changing cursor position
|
||||||
import { StateContext } from '../../context/state'
|
import { StateContext } from '../../context/state'
|
||||||
import { setItems } from '../../actions/actions'
|
import { setItems } from '../../actions/actions'
|
||||||
|
import startsWithCaseInsensitive from '../../utils/startsWithCaseInsensitive'
|
||||||
|
|
||||||
export const useItemsState = (swrData) => {
|
export const useItemsState = (swrData) => {
|
||||||
const { dispatch } = useContext(StateContext)
|
const { dispatch } = useContext(StateContext)
|
||||||
@@ -23,7 +24,6 @@ export const useQueryChange = (query, queryInput, typeaheadInput, onChange) => {
|
|||||||
const value = (() => {
|
const value = (() => {
|
||||||
const currentValue = typeaheadInput.current.value
|
const currentValue = typeaheadInput.current.value
|
||||||
if (!query) return ''
|
if (!query) return ''
|
||||||
//if (!queryMatchesTypeahead(query, currentValue, true)) return ''
|
|
||||||
if (!currentValue.startsWith(query)) return ''
|
if (!currentValue.startsWith(query)) return ''
|
||||||
return currentValue
|
return currentValue
|
||||||
})()
|
})()
|
||||||
@@ -33,4 +33,34 @@ export const useQueryChange = (query, queryInput, typeaheadInput, onChange) => {
|
|||||||
setify(queryInput.current, query)
|
setify(queryInput.current, query)
|
||||||
if (typeof onChange === 'function') onChange(query)
|
if (typeof onChange === 'function') onChange(query)
|
||||||
}, [query, onChange])
|
}, [query, onChange])
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useHighlight = (highlighted, hasFocus, queryInput, typeaheadInput) => {
|
||||||
|
useEffect(() => {
|
||||||
|
const typeAheadValue =
|
||||||
|
highlighted &&
|
||||||
|
hasFocus &&
|
||||||
|
queryInput.current.value.length > 0 &&
|
||||||
|
startsWithCaseInsensitive(highlighted.text, queryInput.current.value)
|
||||||
|
? highlighted.text
|
||||||
|
: ''
|
||||||
|
const queryValue = formatQuery(queryInput.current.value, typeAheadValue)
|
||||||
|
|
||||||
|
typeaheadInput.current.value = typeAheadValue
|
||||||
|
|
||||||
|
setify(queryInput.current, queryValue)
|
||||||
|
}, [highlighted, hasFocus])
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////
|
||||||
|
// Helper functions //
|
||||||
|
//////////////////////////////
|
||||||
|
|
||||||
|
export const formatQuery = (query, typeahead) => {
|
||||||
|
const formattedQuery = typeahead.substring(0, query.length)
|
||||||
|
return formattedQuery.length > 0 &&
|
||||||
|
query.toLowerCase() === formattedQuery.toLowerCase() &&
|
||||||
|
query !== formattedQuery
|
||||||
|
? formattedQuery
|
||||||
|
: query
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
import { vi, describe, test, expect } from 'vitest'
|
import { vi, describe, test, expect } from 'vitest'
|
||||||
import { renderHook } from '@testing-library/react-hooks'
|
import { renderHook } from '@testing-library/react-hooks'
|
||||||
|
import undef from '../../utils/undef'
|
||||||
import {
|
import {
|
||||||
useAutoFocus,
|
useAutoFocus,
|
||||||
useQueryChange
|
useQueryChange,
|
||||||
|
useHighlight,
|
||||||
|
formatQuery
|
||||||
} from './containerEffects'
|
} from './containerEffects'
|
||||||
|
|
||||||
let inputRef = (value = '') => ( //TODO: Put in a beforeEach when blogging
|
let inputRef = (value = '') => ( //TODO: Put in a beforeEach when blogging
|
||||||
@@ -73,4 +76,80 @@ describe('useQueryChange', () => {
|
|||||||
renderHook(() => useQueryChange(queryValue, queryRef, typeaheadRef, onChange))
|
renderHook(() => useQueryChange(queryValue, queryRef, typeaheadRef, onChange))
|
||||||
expect(onChange).toHaveBeenCalledWith(queryValue)
|
expect(onChange).toHaveBeenCalledWith(queryValue)
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('useHighlight', () => {
|
||||||
|
const queryValue = 'chi'
|
||||||
|
const highlighted = {index: 0, text: 'Chicago, Illinois, United States'}
|
||||||
|
|
||||||
|
test('Typeahead is set to highlighted text and query text changes to match case', () => {
|
||||||
|
const queryRef = inputRef(queryValue)
|
||||||
|
const typeaheadRef = inputRef()
|
||||||
|
renderHook(() => useHighlight(highlighted, true, queryRef, typeaheadRef))
|
||||||
|
expect(queryRef.current.value).toBe('Chi')
|
||||||
|
expect(typeaheadRef.current.value).toBe(highlighted.text)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('If there is no highlighted item, no change occurs', () => {
|
||||||
|
const queryRef = inputRef(queryValue)
|
||||||
|
const typeaheadRef = inputRef()
|
||||||
|
renderHook(() => useHighlight(undef, true, queryRef, typeaheadRef))
|
||||||
|
expect(queryRef.current.value).toBe(queryValue)
|
||||||
|
expect(typeaheadRef.current.value).toBe('')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('If focus is not set, no change occurs', () => {
|
||||||
|
const queryRef = inputRef(queryValue)
|
||||||
|
const typeaheadRef = inputRef()
|
||||||
|
renderHook(() => useHighlight(highlighted, false, queryRef, typeaheadRef))
|
||||||
|
expect(queryRef.current.value).toBe(queryValue)
|
||||||
|
expect(typeaheadRef.current.value).toBe('')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('If there is no query, no change occurs', () => {
|
||||||
|
const queryRef = inputRef()
|
||||||
|
const typeaheadRef = inputRef()
|
||||||
|
renderHook(() => useHighlight(highlighted, true, queryRef, typeaheadRef))
|
||||||
|
expect(queryRef.current.value).toBe('')
|
||||||
|
expect(typeaheadRef.current.value).toBe('')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('If selected text does not match typed text, no change occurs', () => {
|
||||||
|
const queryRef = inputRef('Foo')
|
||||||
|
const typeaheadRef = inputRef()
|
||||||
|
renderHook(() => useHighlight(highlighted, true, queryRef, typeaheadRef))
|
||||||
|
expect(queryRef.current.value).toBe('Foo')
|
||||||
|
expect(typeaheadRef.current.value).toBe('')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
//////////////////////////////
|
||||||
|
// Helper functions //
|
||||||
|
//////////////////////////////
|
||||||
|
|
||||||
|
describe('formatQuery', () => {
|
||||||
|
test('If query does not match typeahead, change the case', () => {
|
||||||
|
const formattedQuery = formatQuery('chi', 'Chicago')
|
||||||
|
expect(formattedQuery).toBe('Chi')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('If there is no typeahead, return original query', () => {
|
||||||
|
const formattedQuery = formatQuery('chi', '')
|
||||||
|
expect(formattedQuery).toBe('chi')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('If query and typeahead do not match, return original query', () => {
|
||||||
|
const formattedQuery = formatQuery('chi', 'New York')
|
||||||
|
expect(formattedQuery).toBe('chi')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('If query is blank, return original query', () => {
|
||||||
|
const formattedQuery = formatQuery('', 'Chicago')
|
||||||
|
expect(formattedQuery).toBe('')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('If query matches typeahead, make no change', () => {
|
||||||
|
const formattedQuery = formatQuery('Chi', 'Chicago')
|
||||||
|
expect(formattedQuery).toBe('Chi')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
Reference in New Issue
Block a user