diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index eb6e2e991..ba445ca2b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @yext/backfire \ No newline at end of file +* @yext/watson diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index d962f437d..80b7dc641 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -21,9 +21,9 @@ jobs: runs-on: ubuntu-latest needs: combined_coverage steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Download the current coverage - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: coverage path: coverage/ @@ -43,14 +43,14 @@ jobs: coverage-file: coverage/lcov.info id: parse-combined-coverage - name: Find Comment - uses: peter-evans/find-comment@v2 + uses: peter-evans/find-comment@v3 id: fc with: issue-number: ${{ github.event.pull_request.number }} comment-author: 'github-actions[bot]' body-includes: Current visual coverage - name: Create/Update Comment - uses: peter-evans/create-or-update-comment@v2 + uses: peter-evans/create-or-update-comment@v4 with: comment-id: ${{ steps.fc.outputs.comment-id }} issue-number: ${{ github.event.pull_request.number }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d3cf1dea1..84e8e98dc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -21,5 +21,5 @@ jobs: image: returntocorp/semgrep if: (github.actor != 'dependabot[bot]') steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: semgrep ci diff --git a/.github/workflows/update_snapshots.yml b/.github/workflows/update_snapshots.yml index a0ad3c3fc..36336e2d1 100644 --- a/.github/workflows/update_snapshots.yml +++ b/.github/workflows/update_snapshots.yml @@ -6,11 +6,11 @@ jobs: update_snapshots: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: ref: ${{ github.head_ref }} token: ${{ secrets.BOT_REPO_SCOPED_TOKEN }} - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 18.x registry-url: 'https://registry.npmjs.org' @@ -31,7 +31,7 @@ jobs: default_author: github_actions - name: Fail job if push changes if: steps.push-changes.outputs.pushed == 'true' - uses: actions/github-script@v3 + uses: actions/github-script@v7 with: script: | core.setFailed('Snapshot updates were made. A new commit with new snapshots was pushed.') diff --git a/.storybook/snapshots/__snapshots__/mapboxmap--custom-pin.png b/.storybook/snapshots/__snapshots__/mapboxmap--custom-pin.png index fdddcd74e..14a82b56e 100644 Binary files a/.storybook/snapshots/__snapshots__/mapboxmap--custom-pin.png and b/.storybook/snapshots/__snapshots__/mapboxmap--custom-pin.png differ diff --git a/.storybook/snapshots/__snapshots__/mapboxmap--custom-render-pin.png b/.storybook/snapshots/__snapshots__/mapboxmap--custom-render-pin.png index fdddcd74e..14a82b56e 100644 Binary files a/.storybook/snapshots/__snapshots__/mapboxmap--custom-render-pin.png and b/.storybook/snapshots/__snapshots__/mapboxmap--custom-render-pin.png differ diff --git a/.storybook/snapshots/__snapshots__/mapboxmap--multiple-pins.png b/.storybook/snapshots/__snapshots__/mapboxmap--multiple-pins.png index ede3191c0..f4fb41d9c 100644 Binary files a/.storybook/snapshots/__snapshots__/mapboxmap--multiple-pins.png and b/.storybook/snapshots/__snapshots__/mapboxmap--multiple-pins.png differ diff --git a/.storybook/snapshots/__snapshots__/mapboxmap--primary.png b/.storybook/snapshots/__snapshots__/mapboxmap--primary.png index 84cb61c4e..a4c27597a 100644 Binary files a/.storybook/snapshots/__snapshots__/mapboxmap--primary.png and b/.storybook/snapshots/__snapshots__/mapboxmap--primary.png differ diff --git a/docs/search-ui-react.afterdropdowninputfocusprops.md b/docs/search-ui-react.afterdropdowninputfocusprops.md new file mode 100644 index 000000000..a6b0258a0 --- /dev/null +++ b/docs/search-ui-react.afterdropdowninputfocusprops.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [@yext/search-ui-react](./search-ui-react.md) > [AfterDropdownInputFocusProps](./search-ui-react.afterdropdowninputfocusprops.md) + +## AfterDropdownInputFocusProps interface + +The parameters that are passed into [FilterSearchProps.afterDropdownInputFocus](./search-ui-react.filtersearchprops.afterdropdowninputfocus.md). + +**Signature:** + +```typescript +interface AfterDropdownInputFocusProps +``` + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [value](./search-ui-react.afterdropdowninputfocusprops.value.md) | | string | The input element's value. | + diff --git a/docs/search-ui-react.afterdropdowninputfocusprops.value.md b/docs/search-ui-react.afterdropdowninputfocusprops.value.md new file mode 100644 index 000000000..575738e46 --- /dev/null +++ b/docs/search-ui-react.afterdropdowninputfocusprops.value.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@yext/search-ui-react](./search-ui-react.md) > [AfterDropdownInputFocusProps](./search-ui-react.afterdropdowninputfocusprops.md) > [value](./search-ui-react.afterdropdowninputfocusprops.value.md) + +## AfterDropdownInputFocusProps.value property + +The input element's value. + +**Signature:** + +```typescript +value: string; +``` diff --git a/docs/search-ui-react.filtersearch.md b/docs/search-ui-react.filtersearch.md index ce274298f..9562fae3a 100644 --- a/docs/search-ui-react.filtersearch.md +++ b/docs/search-ui-react.filtersearch.md @@ -9,14 +9,14 @@ A component which allows a user to search for filters associated with specific e **Signature:** ```typescript -declare function FilterSearch({ searchFields, label, placeholder, searchOnSelect, onSelect, onDropdownInputChange, sectioned, customCssClasses }: FilterSearchProps): JSX.Element; +declare function FilterSearch({ searchFields, label, placeholder, searchOnSelect, onSelect, onDropdownInputChange, afterDropdownInputFocus, sectioned, customCssClasses }: FilterSearchProps): JSX.Element; ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | -| { searchFields, label, placeholder, searchOnSelect, onSelect, onDropdownInputChange, sectioned, customCssClasses } | [FilterSearchProps](./search-ui-react.filtersearchprops.md) | | +| { searchFields, label, placeholder, searchOnSelect, onSelect, onDropdownInputChange, afterDropdownInputFocus, sectioned, customCssClasses } | [FilterSearchProps](./search-ui-react.filtersearchprops.md) | | **Returns:** diff --git a/docs/search-ui-react.filtersearchprops.afterdropdowninputfocus.md b/docs/search-ui-react.filtersearchprops.afterdropdowninputfocus.md new file mode 100644 index 000000000..b80d0774d --- /dev/null +++ b/docs/search-ui-react.filtersearchprops.afterdropdowninputfocus.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@yext/search-ui-react](./search-ui-react.md) > [FilterSearchProps](./search-ui-react.filtersearchprops.md) > [afterDropdownInputFocus](./search-ui-react.filtersearchprops.afterdropdowninputfocus.md) + +## FilterSearchProps.afterDropdownInputFocus property + +A function which is called immediately after the input gains focus. It does not replace the default focus behavior. + +**Signature:** + +```typescript +afterDropdownInputFocus?: (params: AfterDropdownInputFocusProps) => void; +``` diff --git a/docs/search-ui-react.filtersearchprops.md b/docs/search-ui-react.filtersearchprops.md index c3f08ccf4..9854423fb 100644 --- a/docs/search-ui-react.filtersearchprops.md +++ b/docs/search-ui-react.filtersearchprops.md @@ -16,6 +16,7 @@ interface FilterSearchProps | Property | Modifiers | Type | Description | | --- | --- | --- | --- | +| [afterDropdownInputFocus?](./search-ui-react.filtersearchprops.afterdropdowninputfocus.md) | | (params: [AfterDropdownInputFocusProps](./search-ui-react.afterdropdowninputfocusprops.md)) => void | _(Optional)_ A function which is called immediately after the input gains focus. It does not replace the default focus behavior. | | [customCssClasses?](./search-ui-react.filtersearchprops.customcssclasses.md) | | [FilterSearchCssClasses](./search-ui-react.filtersearchcssclasses.md) | _(Optional)_ CSS classes for customizing the component styling. | | [label?](./search-ui-react.filtersearchprops.label.md) | | string | _(Optional)_ The display label for the component. | | [onDropdownInputChange?](./search-ui-react.filtersearchprops.ondropdowninputchange.md) | | (params: [OnDropdownInputChangeProps](./search-ui-react.ondropdowninputchangeprops.md)) => void | _(Optional)_ A function which is called when the input element's value changes. Replaces the default behavior. | diff --git a/docs/search-ui-react.md b/docs/search-ui-react.md index a96ec274f..f8907d3f3 100644 --- a/docs/search-ui-react.md +++ b/docs/search-ui-react.md @@ -18,7 +18,7 @@ | [executeSearch(searchActions)](./search-ui-react.executesearch.md) | Executes a universal/vertical search. | | [Facets(props)](./search-ui-react.facets.md) | A component that displays all facets applicable to the current vertical search. | | [FilterDivider({ className })](./search-ui-react.filterdivider.md) | A divider component used to separate NumericalFacets, HierarchicalFacets, StandardFacets, and StaticFilters. | -| [FilterSearch({ searchFields, label, placeholder, searchOnSelect, onSelect, onDropdownInputChange, sectioned, customCssClasses })](./search-ui-react.filtersearch.md) | A component which allows a user to search for filters associated with specific entities and fields. | +| [FilterSearch({ searchFields, label, placeholder, searchOnSelect, onSelect, onDropdownInputChange, afterDropdownInputFocus, sectioned, customCssClasses })](./search-ui-react.filtersearch.md) | A component which allows a user to search for filters associated with specific entities and fields. | | [Geolocation\_2({ geolocationOptions, radius, label, GeolocationIcon, handleClick, customCssClasses, })](./search-ui-react.geolocation_2.md) | A React Component which collects location information to create a location filter and perform a new search. | | [getSearchIntents(searchActions)](./search-ui-react.getsearchintents.md) | Get search intents of the current query stored in headless using autocomplete request. | | [getUserLocation(geolocationOptions)](./search-ui-react.getuserlocation.md) | Retrieves user's location using navigator.geolocation API. | @@ -52,6 +52,7 @@ | Interface | Description | | --- | --- | +| [AfterDropdownInputFocusProps](./search-ui-react.afterdropdowninputfocusprops.md) | The parameters that are passed into [FilterSearchProps.afterDropdownInputFocus](./search-ui-react.filtersearchprops.afterdropdowninputfocus.md). | | [AlternativeVerticalsCssClasses](./search-ui-react.alternativeverticalscssclasses.md) | The CSS class interface used for [AlternativeVerticals()](./search-ui-react.alternativeverticals.md). | | [AlternativeVerticalsProps](./search-ui-react.alternativeverticalsprops.md) | Props for [AlternativeVerticals()](./search-ui-react.alternativeverticals.md). | | [AppliedFiltersCssClasses](./search-ui-react.appliedfilterscssclasses.md) | The CSS class interface used for [AppliedFilters()](./search-ui-react.appliedfilters.md). | diff --git a/etc/search-ui-react.api.md b/etc/search-ui-react.api.md index 188d7529e..52d2507d6 100644 --- a/etc/search-ui-react.api.md +++ b/etc/search-ui-react.api.md @@ -29,6 +29,11 @@ import { UniversalLimit } from '@yext/search-headless-react'; import { UnknownFieldValueDirectAnswer } from '@yext/search-headless-react'; import { VerticalResults as VerticalResults_2 } from '@yext/search-headless-react'; +// @public +export interface AfterDropdownInputFocusProps { + value: string; +} + // @public export function AlternativeVerticals({ currentVerticalLabel, verticalConfigMap, displayAllOnNoResults, customCssClasses }: AlternativeVerticalsProps): JSX.Element | null; @@ -268,7 +273,7 @@ export interface FilterOptionConfig { } // @public -export function FilterSearch({ searchFields, label, placeholder, searchOnSelect, onSelect, onDropdownInputChange, sectioned, customCssClasses }: FilterSearchProps): JSX.Element; +export function FilterSearch({ searchFields, label, placeholder, searchOnSelect, onSelect, onDropdownInputChange, afterDropdownInputFocus, sectioned, customCssClasses }: FilterSearchProps): JSX.Element; // @public export interface FilterSearchCssClasses extends AutocompleteResultCssClasses { @@ -288,6 +293,7 @@ export interface FilterSearchCssClasses extends AutocompleteResultCssClasses { // @public export interface FilterSearchProps { + afterDropdownInputFocus?: (params: AfterDropdownInputFocusProps) => void; customCssClasses?: FilterSearchCssClasses; label?: string; onDropdownInputChange?: (params: OnDropdownInputChangeProps) => void; diff --git a/package-lock.json b/package-lock.json index da7efcc26..74f8b475d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@yext/search-ui-react", - "version": "1.5.0", + "version": "1.6.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@yext/search-ui-react", - "version": "1.5.0", + "version": "1.6.0", "license": "BSD-3-Clause", "dependencies": { "@restart/ui": "^1.0.1", diff --git a/package.json b/package.json index d948661fd..e221d0292 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@yext/search-ui-react", - "version": "1.5.0", + "version": "1.6.0", "description": "A library of React Components for powering Yext Search integrations", "author": "slapshot@yext.com", "license": "BSD-3-Clause", diff --git a/src/components/FilterSearch.tsx b/src/components/FilterSearch.tsx index 18b9b9b04..95b5dc2c4 100644 --- a/src/components/FilterSearch.tsx +++ b/src/components/FilterSearch.tsx @@ -70,6 +70,16 @@ export interface OnDropdownInputChangeProps { executeFilterSearch: (query?: string) => Promise } +/** + * The parameters that are passed into {@link FilterSearchProps.afterDropdownInputFocus}. + * + * @public + */ +export interface AfterDropdownInputFocusProps { + /** The input element's value. */ + value: string, +} + /** * The props for the {@link FilterSearch} component. * @@ -95,6 +105,8 @@ export interface FilterSearchProps { onSelect?: (params: OnSelectParams) => void, /** A function which is called when the input element's value changes. Replaces the default behavior. */ onDropdownInputChange?: (params: OnDropdownInputChangeProps) => void, + /** A function which is called immediately after the input gains focus. It does not replace the default focus behavior. */ + afterDropdownInputFocus?: (params: AfterDropdownInputFocusProps) => void, /** Determines whether or not the results of the filter search are separated by field. Defaults to false. */ sectioned?: boolean, /** CSS classes for customizing the component styling. */ @@ -116,6 +128,7 @@ export function FilterSearch({ searchOnSelect, onSelect, onDropdownInputChange, + afterDropdownInputFocus, sectioned = false, customCssClasses }: FilterSearchProps): JSX.Element { @@ -293,7 +306,9 @@ export function FilterSearch({ if (value) { executeFilterSearch(value); } - }, [executeFilterSearch]); + + afterDropdownInputFocus?.({value}); + }, [afterDropdownInputFocus, executeFilterSearch]); return (
diff --git a/src/components/index.ts b/src/components/index.ts index a5c8101a8..eb7664764 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -37,7 +37,8 @@ export { FilterSearchCssClasses, FilterSearchProps, OnSelectParams, - OnDropdownInputChangeProps + OnDropdownInputChangeProps, + AfterDropdownInputFocusProps, } from './FilterSearch'; export { diff --git a/test-site/package-lock.json b/test-site/package-lock.json index e5aad9d10..e8570c397 100644 --- a/test-site/package-lock.json +++ b/test-site/package-lock.json @@ -33,7 +33,7 @@ }, "..": { "name": "@yext/search-ui-react", - "version": "1.5.0", + "version": "1.6.0-beta.432", "license": "BSD-3-Clause", "dependencies": { "@restart/ui": "^1.0.1", diff --git a/tests/components/FilterSearch.stories.tsx b/tests/components/FilterSearch.stories.tsx index 3f726af28..370688518 100644 --- a/tests/components/FilterSearch.stories.tsx +++ b/tests/components/FilterSearch.stories.tsx @@ -42,6 +42,7 @@ const meta: Meta = { args: { label: 'Filter', onDropdownInputChange: undefined, + afterDropdownInputFocus: undefined, } }; export default meta; diff --git a/tests/components/FilterSearch.test.tsx b/tests/components/FilterSearch.test.tsx index e3273af03..ae8fe8995 100644 --- a/tests/components/FilterSearch.test.tsx +++ b/tests/components/FilterSearch.test.tsx @@ -136,10 +136,10 @@ describe('search with section labels', () => { await userEvent.type(searchBarElement, 'n'); expect(executeFilterSearch).toHaveBeenCalled(); - + const autocompleteSection = screen.getByText('First name'); expect(autocompleteSection).toBeDefined(); - + }); it('input value stays the same when a user selects a filter', async () => { @@ -193,7 +193,7 @@ describe('search with section labels', () => { }, selected: false }); - + expect(setFilterOption).toBeCalledWith({ filter: { kind: 'fieldValue', @@ -650,7 +650,7 @@ describe('search without section labels', () => { await userEvent.keyboard('{enter}'); expect(inputNode).toHaveValue('first name 1'); }); - + it('when an onDropdownInputChange prop is specified, it gets called each time after the input changes and executeFilterSearch does not', async () => { const mockedOnDropdownInputChange = jest.fn(); const executeFilterSearch = jest @@ -659,7 +659,31 @@ describe('search without section labels', () => { await userEvent.type(screen.getByRole('textbox'), 'a'); expect(mockedOnDropdownInputChange).toHaveBeenCalledTimes(1); expect(executeFilterSearch).toHaveBeenCalledTimes(0); - }) + }); + + it('when an afterDropdownInputFocus prop is provided, invokes it in addition to the original ' + + 'behavior when input gains focus', async () => { + const mockedAfterDropdownInputFocus = jest.fn(); + const executeFilterSearch = jest.spyOn(SearchHeadless.prototype, 'executeFilterSearch'); + renderFilterSearch( + {searchFields: searchFieldsProp, afterDropdownInputFocus: mockedAfterDropdownInputFocus}); + + // Click into input. ExecuteFilterSearch wouldn't be triggered since the input is empty. + await userEvent.click(screen.getByRole('textbox')); + expect(mockedAfterDropdownInputFocus).toHaveBeenCalledTimes(1); + expect(executeFilterSearch).toHaveBeenCalledTimes(0); + + // Update input. + await userEvent.type(screen.getByRole('textbox'), 'a'); + expect(executeFilterSearch).toHaveBeenCalledTimes(1); + + // Click out of input and then click into input. + // ExecuteFilterSearch would be triggered since input no longer empty. + await userEvent.click(document.body); + await userEvent.click(screen.getByRole('textbox')); + expect(executeFilterSearch).toHaveBeenCalledTimes(2); + expect(mockedAfterDropdownInputFocus).toHaveBeenCalledTimes(2); + }); }); describe('screen reader', () => { @@ -675,7 +699,7 @@ describe('screen reader', () => { expect(executeFilterSearch).toHaveBeenCalled(); const expectedScreenReaderMessage = '2 First name autocomplete options found. 1 Last name autocomplete option found.'; - + const screenReaderMessage = screen.getByText(expectedScreenReaderMessage); expect(screenReaderMessage).toBeDefined(); }); @@ -693,7 +717,7 @@ describe('screen reader', () => { const expectedScreenReaderMessage = '3 autocomplete options found.'; const screenReaderMessage = screen.getByText(expectedScreenReaderMessage); - + expect(screenReaderMessage).toBeDefined(); }); @@ -709,7 +733,7 @@ describe('screen reader', () => { const expectedScreenReaderMessage = '0 autocomplete options found.'; const screenReaderMessage = screen.getByText(expectedScreenReaderMessage); - + expect(screenReaderMessage).toBeDefined(); }); });