From 2b1bd141facfaa589f9964b94d1be8b006532c9b Mon Sep 17 00:00:00 2001 From: Tom Southall Date: Wed, 6 Apr 2022 22:27:33 +0100 Subject: [PATCH] Expose DOM methods via a forwarded ref --- CHANGELOG.md | 3 ++ README.md | 57 ++++++++++++++++++++++++++++++++ examples/geo/App.jsx | 4 ++- package-lock.json | 18 +++++----- package.json | 4 +-- src/lib/components/container.jsx | 33 ++++++++++++++++-- src/lib/index.jsx | 16 +++++---- 7 files changed, 114 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f26f265..fdc8fc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +### 1.4.0 (6 Apr 2022) +- Expose DOM methods via a forwarded ref + ### 1.3.0 (2 Apr 2022) - Extend listbox and defaultListbox prop types to allow functions returning a promise resolving to an array of group settings diff --git a/README.md b/README.md index ec6da91..ff1fa9b 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Turnstone is a highly customisable, easy-to-use autocomplete search component fo - [API](#api) - [Props](#props) - [Component Props](#component-props) + - [Methods](#methods) - [Contributing](#contributing) - [Release Checklist](#release-checklist) - [License](#license) @@ -624,6 +625,62 @@ The following custom components can also be supplied as props: - **`id`** (string) The `id` of the group supplied in the `listbox` or `defaultListbox` prop. - **`index`** (number) The index of the group. Matches the order supplied in the `listbox` or `defaultListbox` prop. Zero-indexed. +### Methods + +There are a number of methods accessible via a ref supplied to the Turnstone component. + +For example: + +```jsx +import React, { useRef } from 'react' +import Turnstone from 'turnstone' +import data from './data' + +const App = () => { + const listbox = { data } + + const turnstoneRef = useRef() + + const handleQuery = () => { + turnstoneRef.current?.query('new') + } + + const handleClear = () => { + turnstoneRef.current?.clear() + } + + return ( + <> + + + + + ) +} +``` + +The methods are as follows: + +#### `blur()` + +Removes keyboard focus from the search box. + +#### `clear()` + +Clears the contents of the search box + +#### `focus()` + +Sets keyboard focus on the search box. + +#### `query()` + +Sets the search box contents to the string argument supplied to the function. + +#### `select()` + +Selects the contents of the search box + ## Contributing - Fork the project - Run the project in development mode: `$ npm run dev` diff --git a/examples/geo/App.jsx b/examples/geo/App.jsx index dd136b4..50857a8 100644 --- a/examples/geo/App.jsx +++ b/examples/geo/App.jsx @@ -1,6 +1,6 @@ /* eslint no-console: 0 */ -import React, { useCallback, useState } from 'react' +import React, { useCallback, useState, useRef } from 'react' import Turnstone from '../../src/lib' import styles from './styles/App.module.css' import autocompleteStyles from './styles/autocomplete.module.css' @@ -71,6 +71,7 @@ const listbox = [ // } const App = () => { + const ref = useRef() const [selectedItem, setSelectedItem] = useState(undef) const onSelect = useCallback( @@ -98,6 +99,7 @@ const App = () => {
  { // Destructure props const { autoFocus, @@ -202,6 +202,29 @@ export default function Container(props) { setBlockBlurHandler(false) } + // Expose methods to parent components + useImperativeHandle(ref, () => ({ + focus: () => { + queryInput.current.focus() + }, + blur: () => { + queryInput.current.blur() + }, + select: () => { + queryInput.current.select() + }, + clear: () => { + clearState() + }, + query: (query) => { + if(typeof query === 'string') { + queryInput.current.value = query + queryInput.current.focus() + handleInput() + } + } + })) + return (
) -} +}) + +Container.displayName = 'Container' + +export default Container diff --git a/src/lib/index.jsx b/src/lib/index.jsx index 4836cec..6db85c6 100644 --- a/src/lib/index.jsx +++ b/src/lib/index.jsx @@ -28,12 +28,12 @@ const propDefaults = { Clear: () => '\u00d7' } -const render = (Component, componentProps, pluginIndex) => { +const render = (Component, componentProps, pluginIndex, ref) => { const p = Array.isArray(componentProps.plugins) && componentProps.plugins[pluginIndex] if(p) { const [Plugin, pluginProps] = Array.isArray(p) ? p : [p] - return { render }} /> } - return + return } -export default function Turnstone(props) { +const Turnstone = React.forwardRef((props, ref) => { const componentProps = {...propDefaults, ...props} return ( - { render(Container, componentProps, 0) } + { render(Container, componentProps, 0, ref) } ) -} +}) + +Turnstone.displayName = 'Turnstone' + +export default Turnstone ////////////////////////////////////////////////////// // Prop validation //