@open-pioneer/search 1.3.0-dev-map-loading.20260202144650 → 1.3.0-dev-css-prop.20260210130215

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,12 +1,25 @@
1
1
  # @open-pioneer/search
2
2
 
3
- ## 1.3.0-dev-map-loading.20260202144650
3
+ ## 1.3.0-dev-css-prop.20260210130215
4
+
5
+ ### Minor Changes
6
+
7
+ - 911fda5: Support for new common container props (role, aria-_, data-_ and css)
8
+ - d05a3b8: Refactor user interface: now based on chakra's combobox instead of react-select.
9
+
10
+ NOTE: This causes changes to many internal CSS classes and attributes.
11
+ The UX of the control should be the same.
12
+
13
+ - d54ccfd: Update to Chakra UI 3.32.0
4
14
 
5
15
  ### Patch Changes
6
16
 
7
- - Updated dependencies [f5030cc]
8
- - Updated dependencies [f5030cc]
9
- - @open-pioneer/map@1.3.0-dev-map-loading.20260202144650
17
+ - Updated dependencies [911fda5]
18
+ - Updated dependencies [2ceb1ca]
19
+ - Updated dependencies [d54ccfd]
20
+ - Updated dependencies [4bcc8ce]
21
+ - Updated dependencies [2ceb1ca]
22
+ - @open-pioneer/map@1.3.0-dev-css-prop.20260210130215
10
23
 
11
24
  ## 1.2.0
12
25
 
@@ -1,6 +1,6 @@
1
- const sourceId$1 = "@open-pioneer/search/Search";
1
+ const sourceId$1 = "@open-pioneer/search/ui/useSearchState";
2
2
 
3
- const sourceId = "@open-pioneer/search/SearchController";
3
+ const sourceId = "@open-pioneer/search/model/SearchController";
4
4
 
5
- export { sourceId$1 as sourceId, sourceId as sourceId$1 };
5
+ export { sourceId, sourceId$1 };
6
6
  //# sourceMappingURL=source-info.js.map
package/i18n/de.yaml CHANGED
@@ -1,6 +1,7 @@
1
1
  messages:
2
2
  noOptionsText: "Keine Suchtreffer gefunden"
3
3
  loadingText: "Frage Daten ab..."
4
+ resultLoaded: "Ergebnisse geladen"
4
5
  searchPlaceholder: "Suche..."
5
6
  ariaLabel:
6
7
  search: "Suchleiste"
package/i18n/en.yaml CHANGED
@@ -1,6 +1,7 @@
1
1
  messages:
2
2
  noOptionsText: "No results found"
3
3
  loadingText: "Loading..."
4
+ resultLoaded: "Results loaded"
4
5
  searchPlaceholder: "Search..."
5
6
  ariaLabel:
6
7
  search: "Search bar"
package/index.d.ts CHANGED
@@ -1,3 +1,2 @@
1
1
  export type { SearchApi, SearchClearEvent, SearchClearTrigger, SearchDisposedEvent, SearchOptions, SearchReadyEvent, SearchResult, SearchSelectEvent, SearchSource } from "./api";
2
- export { Search, type SearchProps } from "./Search";
3
- export { SearchApiImpl } from "./SearchApiImpl";
2
+ export { Search, type SearchProps } from "./ui/Search";
package/index.js CHANGED
@@ -1,3 +1,2 @@
1
- export { Search } from './Search.js';
2
- export { SearchApiImpl } from './SearchApiImpl.js';
1
+ export { Search } from './ui/Search.js';
3
2
  //# sourceMappingURL=index.js.map
package/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
@@ -1,9 +1,9 @@
1
1
  import { MapModel } from "@open-pioneer/map";
2
- import { SearchResult, SearchSource } from "./api";
2
+ import { SearchResult, SearchSource } from "../api";
3
3
  /**
4
4
  * Group of suggestions returned from one source.
5
5
  */
6
- export interface SuggestionGroup {
6
+ export interface ResultGroup {
7
7
  label: string;
8
8
  source: SearchSource;
9
9
  results: SearchResult[];
@@ -12,7 +12,7 @@ export declare class SearchController {
12
12
  #private;
13
13
  constructor(mapModel: MapModel, sources: SearchSource[]);
14
14
  destroy(): void;
15
- search(searchTerm: string): Promise<SuggestionGroup[]>;
15
+ search(searchTerm: string): Promise<ResultGroup[]>;
16
16
  get searchTypingDelay(): number;
17
17
  set searchTypingDelay(value: number | undefined);
18
18
  get maxResultsPerSource(): number;
@@ -1,5 +1,5 @@
1
1
  import { createLogger, throwAbortError, isAbortError } from '@open-pioneer/core';
2
- import { sourceId$1 as sourceId } from './_virtual/source-info.js';
2
+ import { sourceId } from '../_virtual/source-info.js';
3
3
 
4
4
  const LOG = createLogger(sourceId);
5
5
  const DEFAULT_SEARCH_TYPING_DELAY = 200;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SearchController.js","sources":["SearchController.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2023-2025 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\nimport { createLogger, isAbortError, throwAbortError } from \"@open-pioneer/core\";\nimport { MapModel } from \"@open-pioneer/map\";\nimport { sourceId } from \"open-pioneer:source-info\";\nimport { SearchResult, SearchSource } from \"../api\";\n\nconst LOG = createLogger(sourceId);\n\n/**\n * Group of suggestions returned from one source.\n */\nexport interface ResultGroup {\n label: string;\n source: SearchSource;\n results: SearchResult[];\n}\n\nconst DEFAULT_SEARCH_TYPING_DELAY = 200;\nconst DEFAULT_MAX_RESULTS_PER_SOURCE = 5;\n\nexport class SearchController {\n #mapModel: MapModel;\n\n /**\n * Search sources defined by the developer.\n */\n #sources: SearchSource[] = [];\n\n /**\n * Limits the number of results per source.\n */\n #maxResultsPerSource: number = DEFAULT_MAX_RESULTS_PER_SOURCE;\n\n /**\n * The timeout in millis.\n */\n #searchTypingDelay: number = DEFAULT_SEARCH_TYPING_DELAY;\n\n /**\n * Cancel or abort a previous request.\n */\n #abortController: AbortController | undefined;\n\n constructor(mapModel: MapModel, sources: SearchSource[]) {\n this.#mapModel = mapModel;\n this.#sources = sources;\n }\n\n destroy() {\n this.#abortController?.abort();\n this.#abortController = undefined;\n }\n\n async search(searchTerm: string): Promise<ResultGroup[]> {\n this.#abortController?.abort();\n this.#abortController = undefined;\n if (!searchTerm) {\n return [];\n }\n\n const abort = (this.#abortController = new AbortController());\n try {\n await waitForTimeOut(abort.signal, this.#searchTypingDelay);\n if (abort.signal.aborted) {\n LOG.debug(`search canceled with ${searchTerm}`);\n throwAbortError();\n }\n const settledSearches = await Promise.all(\n this.#sources.map((source) => this.#searchSource(source, searchTerm, abort.signal))\n );\n return settledSearches.filter((s): s is ResultGroup => s != null);\n } finally {\n if (this.#abortController === abort) {\n this.#abortController = undefined;\n }\n }\n }\n\n async #searchSource(\n source: SearchSource,\n searchTerm: string,\n signal: AbortSignal\n ): Promise<ResultGroup | undefined> {\n const label = source.label;\n const projection = this.#mapModel.projection;\n try {\n const maxResults = this.#maxResultsPerSource;\n let results = await source.search(searchTerm, {\n maxResults,\n signal,\n mapProjection: projection,\n map: this.#mapModel\n });\n if (results.length > maxResults) {\n results = results.slice(0, maxResults);\n }\n return { label, source, results };\n } catch (e) {\n if (!isAbortError(e)) {\n LOG.error(`search for source ${label} failed`, e);\n }\n return undefined;\n }\n }\n\n get searchTypingDelay(): number {\n return this.#searchTypingDelay;\n }\n\n set searchTypingDelay(value: number | undefined) {\n this.#searchTypingDelay = value ?? DEFAULT_SEARCH_TYPING_DELAY;\n }\n\n get maxResultsPerSource(): number {\n return this.#maxResultsPerSource;\n }\n\n set maxResultsPerSource(value: number | undefined) {\n this.#maxResultsPerSource = value ?? DEFAULT_MAX_RESULTS_PER_SOURCE;\n }\n\n get sources() {\n return this.#sources;\n }\n}\n\n/**\n * wait for timeouts millis or until signal is aborted, whatever happens first\n */\nasync function waitForTimeOut(signal: AbortSignal, timeoutMillis: number) {\n if (signal.aborted) {\n return;\n }\n\n await new Promise<void>((resolve) => {\n const done = () => {\n signal.removeEventListener(\"abort\", done);\n clearTimeout(timeoutId);\n resolve();\n };\n\n signal.addEventListener(\"abort\", done);\n const timeoutId = setTimeout(done, timeoutMillis);\n });\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@open-pioneer/search",
4
- "version": "1.3.0-dev-map-loading.20260202144650",
4
+ "version": "1.3.0-dev-css-prop.20260210130215",
5
5
  "description": "This package provides a UI component to perform a search on given search sources.",
6
6
  "keywords": [
7
7
  "open-pioneer-trails"
@@ -14,28 +14,25 @@
14
14
  "directory": "src/packages/search"
15
15
  },
16
16
  "dependencies": {
17
- "@chakra-ui/react": "^3.31.0",
18
- "@open-pioneer/chakra-snippets": "^4.4.0",
19
- "@open-pioneer/core": "^4.4.0",
20
- "@open-pioneer/react-utils": "^4.4.0",
21
- "@open-pioneer/runtime": "^4.4.0",
22
- "chakra-react-select": "^6.1.1",
17
+ "@chakra-ui/react": "^3.32.0",
18
+ "@open-pioneer/chakra-snippets": "4.5.0-dev-css-prop.20260210120105",
19
+ "@open-pioneer/core": "4.5.0-dev-css-prop.20260210120105",
20
+ "@open-pioneer/react-utils": "4.5.0-dev-css-prop.20260210120105",
21
+ "@open-pioneer/runtime": "4.5.0-dev-css-prop.20260210120105",
23
22
  "classnames": "^2.5.1",
24
23
  "ol": "^10.7.0",
25
- "react": "^19.2.3",
24
+ "react": "^19.2.4",
26
25
  "react-icons": "^5.5.0",
27
- "@open-pioneer/map": "1.3.0-dev-map-loading.20260202144650"
26
+ "@open-pioneer/map": "1.3.0-dev-css-prop.20260210130215"
28
27
  },
29
28
  "exports": {
30
29
  "./package.json": "./package.json",
31
30
  ".": {
32
31
  "import": "./index.js",
33
32
  "types": "./index.d.ts"
34
- },
35
- "./search.css": "./search.css"
33
+ }
36
34
  },
37
35
  "openPioneerFramework": {
38
- "styles": "./search.css",
39
36
  "services": [],
40
37
  "i18n": {
41
38
  "languages": [
@@ -1,23 +1,7 @@
1
1
  import { MapModelProps } from "@open-pioneer/map";
2
2
  import { CommonComponentProps } from "@open-pioneer/react-utils";
3
3
  import { FC } from "react";
4
- import { SearchClearEvent, SearchDisposedEvent, SearchReadyEvent, SearchResult, SearchSelectEvent, SearchSource } from "./api";
5
- export interface SearchOption {
6
- /** Unique value for this option. */
7
- value: string;
8
- /** Display text shown in menu. */
9
- label: string;
10
- /** Search source that returned the suggestion. */
11
- source: SearchSource;
12
- /** The raw result from the search source. */
13
- result: SearchResult;
14
- }
15
- export interface SearchGroupOption {
16
- /** Display text shown in menu. */
17
- label: string;
18
- /** Set of options that belong to this group. */
19
- options: SearchOption[];
20
- }
4
+ import { SearchClearEvent, SearchDisposedEvent, SearchReadyEvent, SearchSelectEvent, SearchSource } from "../api";
21
5
  /**
22
6
  * Properties supported by the {@link Search} component.
23
7
  */
package/ui/Search.js ADDED
@@ -0,0 +1,69 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import { Box } from '@chakra-ui/react';
3
+ import { useMapModelValue } from '@open-pioneer/map';
4
+ import { useCommonComponentProps, useEvent } from '@open-pioneer/react-utils';
5
+ import { useRef, useEffect } from 'react';
6
+ import { SearchApiImpl } from './SearchApiImpl.js';
7
+ import { SearchInput } from './SearchInput.js';
8
+ import { useSearchState } from './useSearchState.js';
9
+
10
+ const Search = (props) => {
11
+ const {
12
+ sources,
13
+ searchTypingDelay,
14
+ maxResultsPerGroup,
15
+ placeholder,
16
+ onSelect,
17
+ onClear,
18
+ onReady,
19
+ onDisposed
20
+ } = props;
21
+ const { containerProps } = useCommonComponentProps("search", props);
22
+ const map = useMapModelValue(props);
23
+ const { input, results, onInputChanged, onOptionConfirmed, selectedOption } = useSearchState(
24
+ sources,
25
+ searchTypingDelay,
26
+ maxResultsPerGroup,
27
+ map
28
+ );
29
+ useSearchApi(onReady, onDisposed, onInputChanged, onClear);
30
+ return /* @__PURE__ */ jsx(Box, { ...containerProps, width: "100%", children: /* @__PURE__ */ jsx(
31
+ SearchInput,
32
+ {
33
+ input,
34
+ selectedOption,
35
+ results,
36
+ placeholder,
37
+ onClear,
38
+ onSelect,
39
+ onInputChanged,
40
+ onOptionConfirmed
41
+ }
42
+ ) });
43
+ };
44
+ function useSearchApi(onReady, onDisposed, onInputChanged, onClear) {
45
+ const clearInput = useEvent(() => {
46
+ onInputChanged("");
47
+ onClear?.({ trigger: "api-reset" });
48
+ });
49
+ const apiRef = useRef(null);
50
+ if (!apiRef.current) {
51
+ apiRef.current = new SearchApiImpl(clearInput, onInputChanged);
52
+ }
53
+ const api = apiRef.current;
54
+ const readyTrigger = useEvent(() => {
55
+ onReady?.({
56
+ api
57
+ });
58
+ });
59
+ const disposeTrigger = useEvent(() => {
60
+ onDisposed?.({});
61
+ });
62
+ useEffect(() => {
63
+ readyTrigger();
64
+ return disposeTrigger;
65
+ }, [readyTrigger, disposeTrigger]);
66
+ }
67
+
68
+ export { Search };
69
+ //# sourceMappingURL=Search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Search.js","sources":["Search.tsx"],"sourcesContent":["// SPDX-FileCopyrightText: 2023-2025 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\nimport { Box } from \"@chakra-ui/react\";\nimport { MapModelProps, useMapModelValue } from \"@open-pioneer/map\";\nimport { CommonComponentProps, useCommonComponentProps, useEvent } from \"@open-pioneer/react-utils\";\nimport { FC, useEffect, useRef } from \"react\";\nimport {\n SearchApi,\n SearchClearEvent,\n SearchDisposedEvent,\n SearchReadyEvent,\n SearchSelectEvent,\n SearchSource\n} from \"../api\";\nimport { SearchApiImpl } from \"./SearchApiImpl\";\nimport { SearchInput } from \"./SearchInput\";\nimport { useSearchState } from \"./useSearchState\";\n\n/**\n * Properties supported by the {@link Search} component.\n */\nexport interface SearchProps extends CommonComponentProps, MapModelProps {\n /**\n * Data sources to be searched on.\n */\n sources: SearchSource[];\n\n /**\n * Typing delay (in milliseconds) before the async search query starts after the user types in the search term.\n * Defaults to `200`.\n */\n searchTypingDelay?: number;\n\n /**\n * The maximum number of results shown per group.\n * Defaults to `5`.\n */\n maxResultsPerGroup?: number;\n\n /**\n * The placeholder text shown in the search input field when it is empty.\n * Defaults to a generic (and localized) hint.\n */\n placeholder?: string;\n\n /**\n * This event handler will be called when the user selects a search result.\n */\n onSelect?: (event: SearchSelectEvent) => void;\n\n /**\n * This event handler will be called when the user clears the search input.\n */\n onClear?: (event: SearchClearEvent) => void;\n\n /**\n * Callback that is triggered once when the search is initialized.\n * The search API can be accessed by the `api` property of the {@link SearchReadyEvent}.\n */\n onReady?: (event: SearchReadyEvent) => void;\n\n /**\n * Callback that is triggered once when the search is disposed and unmounted.\n */\n onDisposed?: (event: SearchDisposedEvent) => void;\n}\n\n/**\n * A component that allows the user to search a given set of {@link SearchSource | SearchSources}.\n */\nexport const Search: FC<SearchProps> = (props) => {\n const {\n sources,\n searchTypingDelay,\n maxResultsPerGroup,\n placeholder,\n onSelect,\n onClear,\n onReady,\n onDisposed\n } = props;\n const { containerProps } = useCommonComponentProps(\"search\", props);\n const map = useMapModelValue(props);\n const { input, results, onInputChanged, onOptionConfirmed, selectedOption } = useSearchState(\n sources,\n searchTypingDelay,\n maxResultsPerGroup,\n map\n );\n\n // api trigger hooks\n useSearchApi(onReady, onDisposed, onInputChanged, onClear);\n\n return (\n <Box {...containerProps} width={\"100%\"}>\n <SearchInput\n input={input}\n selectedOption={selectedOption}\n results={results}\n placeholder={placeholder}\n onClear={onClear}\n onSelect={onSelect}\n onInputChanged={onInputChanged}\n onOptionConfirmed={onOptionConfirmed}\n />\n </Box>\n );\n};\n\n// Note: `clearInput` and `setInputValue` must be stable because only the initial value is used to construct the API instance at this time.\nfunction useSearchApi(\n onReady: ((event: SearchReadyEvent) => void) | undefined,\n onDisposed: ((event: SearchDisposedEvent) => void) | undefined,\n onInputChanged: (newValue: string) => void,\n onClear: ((event: SearchClearEvent) => void) | undefined\n) {\n const clearInput = useEvent(() => {\n onInputChanged(\"\");\n onClear?.({ trigger: \"api-reset\" });\n });\n\n const apiRef = useRef<SearchApi>(null);\n if (!apiRef.current) {\n // NOTE: The API object is only created once.\n // This is why the functions must currently be stable!\n apiRef.current = new SearchApiImpl(clearInput, onInputChanged);\n }\n\n const api = apiRef.current;\n\n const readyTrigger = useEvent(() => {\n onReady?.({\n api\n });\n });\n\n const disposeTrigger = useEvent(() => {\n onDisposed?.({});\n });\n\n // Trigger ready / dispose on mount / unmount, but if the callbacks change.\n // useEvent() returns a stable function reference.\n useEffect(() => {\n readyTrigger();\n return disposeTrigger;\n }, [readyTrigger, disposeTrigger]);\n}\n"],"names":[],"mappings":";;;;;;;;;AAsEO,MAAM,MAAA,GAA0B,CAAC,KAAA,KAAU;AAC9C,EAAA,MAAM;AAAA,IACF,OAAA;AAAA,IACA,iBAAA;AAAA,IACA,kBAAA;AAAA,IACA,WAAA;AAAA,IACA,QAAA;AAAA,IACA,OAAA;AAAA,IACA,OAAA;AAAA,IACA;AAAA,GACJ,GAAI,KAAA;AACJ,EAAA,MAAM,EAAE,cAAA,EAAe,GAAI,uBAAA,CAAwB,UAAU,KAAK,CAAA;AAClE,EAAA,MAAM,GAAA,GAAM,iBAAiB,KAAK,CAAA;AAClC,EAAA,MAAM,EAAE,KAAA,EAAO,OAAA,EAAS,cAAA,EAAgB,iBAAA,EAAmB,gBAAe,GAAI,cAAA;AAAA,IAC1E,OAAA;AAAA,IACA,iBAAA;AAAA,IACA,kBAAA;AAAA,IACA;AAAA,GACJ;AAGA,EAAA,YAAA,CAAa,OAAA,EAAS,UAAA,EAAY,cAAA,EAAgB,OAAO,CAAA;AAEzD,EAAA,uBACI,GAAA,CAAC,GAAA,EAAA,EAAK,GAAG,cAAA,EAAgB,OAAO,MAAA,EAC5B,QAAA,kBAAA,GAAA;AAAA,IAAC,WAAA;AAAA,IAAA;AAAA,MACG,KAAA;AAAA,MACA,cAAA;AAAA,MACA,OAAA;AAAA,MACA,WAAA;AAAA,MACA,OAAA;AAAA,MACA,QAAA;AAAA,MACA,cAAA;AAAA,MACA;AAAA;AAAA,GACJ,EACJ,CAAA;AAER;AAGA,SAAS,YAAA,CACL,OAAA,EACA,UAAA,EACA,cAAA,EACA,OAAA,EACF;AACE,EAAA,MAAM,UAAA,GAAa,SAAS,MAAM;AAC9B,IAAA,cAAA,CAAe,EAAE,CAAA;AACjB,IAAA,OAAA,GAAU,EAAE,OAAA,EAAS,WAAA,EAAa,CAAA;AAAA,EACtC,CAAC,CAAA;AAED,EAAA,MAAM,MAAA,GAAS,OAAkB,IAAI,CAAA;AACrC,EAAA,IAAI,CAAC,OAAO,OAAA,EAAS;AAGjB,IAAA,MAAA,CAAO,OAAA,GAAU,IAAI,aAAA,CAAc,UAAA,EAAY,cAAc,CAAA;AAAA,EACjE;AAEA,EAAA,MAAM,MAAM,MAAA,CAAO,OAAA;AAEnB,EAAA,MAAM,YAAA,GAAe,SAAS,MAAM;AAChC,IAAA,OAAA,GAAU;AAAA,MACN;AAAA,KACH,CAAA;AAAA,EACL,CAAC,CAAA;AAED,EAAA,MAAM,cAAA,GAAiB,SAAS,MAAM;AAClC,IAAA,UAAA,GAAa,EAAE,CAAA;AAAA,EACnB,CAAC,CAAA;AAID,EAAA,SAAA,CAAU,MAAM;AACZ,IAAA,YAAA,EAAa;AACb,IAAA,OAAO,cAAA;AAAA,EACX,CAAA,EAAG,CAAC,YAAA,EAAc,cAAc,CAAC,CAAA;AACrC;;;;"}
@@ -1,4 +1,4 @@
1
- import { SearchApi, SearchClearTrigger } from "./api";
1
+ import { SearchApi, SearchClearTrigger } from "../api";
2
2
  export declare class SearchApiImpl implements SearchApi {
3
3
  #private;
4
4
  constructor(clearInput: (trigger: SearchClearTrigger) => void, setInputValue: (newValue: string) => void);
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SearchApiImpl.js","sources":["SearchApiImpl.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2023-2025 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\nimport { SearchApi, SearchClearTrigger } from \"../api\";\n\nexport class SearchApiImpl implements SearchApi {\n #clearInput: (trigger: SearchClearTrigger) => void;\n #setInputValue: (newValue: string) => void;\n\n constructor(\n clearInput: (trigger: SearchClearTrigger) => void,\n setInputValue: (newValue: string) => void\n ) {\n this.#clearInput = clearInput;\n this.#setInputValue = setInputValue;\n }\n\n resetInput() {\n this.#clearInput(\"api-reset\");\n }\n\n setInputValue(inputValue: string) {\n this.#setInputValue(inputValue);\n }\n}\n"],"names":[],"mappings":"AAIO,MAAM,aAAA,CAAmC;AAAA,EAC5C,WAAA;AAAA,EACA,cAAA;AAAA,EAEA,WAAA,CACI,YACA,aAAA,EACF;AACE,IAAA,IAAA,CAAK,WAAA,GAAc,UAAA;AACnB,IAAA,IAAA,CAAK,cAAA,GAAiB,aAAA;AAAA,EAC1B;AAAA,EAEA,UAAA,GAAa;AACT,IAAA,IAAA,CAAK,YAAY,WAAW,CAAA;AAAA,EAChC;AAAA,EAEA,cAAc,UAAA,EAAoB;AAC9B,IAAA,IAAA,CAAK,eAAe,UAAU,CAAA;AAAA,EAClC;AACJ;;;;"}
@@ -0,0 +1,13 @@
1
+ import { SearchClearEvent, SearchSelectEvent } from "../api";
2
+ import { SearchOption, SearchResultsState } from "./useSearchState";
3
+ export interface SearchInputProps {
4
+ input: string;
5
+ selectedOption?: SearchOption;
6
+ results: SearchResultsState;
7
+ placeholder?: string;
8
+ onClear?: (event: SearchClearEvent) => void;
9
+ onSelect?: (event: SearchSelectEvent) => void;
10
+ onInputChanged: (newInput: string) => void;
11
+ onOptionConfirmed: (option: SearchOption) => void;
12
+ }
13
+ export declare function SearchInput(props: SearchInputProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,143 @@
1
+ import { jsx, jsxs } from 'react/jsx-runtime';
2
+ import { VisuallyHidden, CloseButton, Combobox, InputGroup, Icon, Spinner, Portal, createListCollection } from '@chakra-ui/react';
3
+ import { Tooltip } from '@open-pioneer/chakra-snippets/tooltip';
4
+ import { useEvent } from '@open-pioneer/react-utils';
5
+ import { useIntl } from '../_virtual/hooks.js';
6
+ import { memo, useRef, useMemo } from 'react';
7
+ import { LuSearch } from 'react-icons/lu';
8
+ import { SearchResults } from './SearchResults.js';
9
+
10
+ function SearchInput(props) {
11
+ const {
12
+ input,
13
+ selectedOption,
14
+ results,
15
+ placeholder,
16
+ onClear,
17
+ onSelect,
18
+ onInputChanged,
19
+ onOptionConfirmed
20
+ } = props;
21
+ const intl = useIntl();
22
+ const controlRef = useRef(null);
23
+ const collection = useSearchCollection(results);
24
+ const { handleInputChange, handleSelectChange } = useSearchHandlers(
25
+ onInputChanged,
26
+ onOptionConfirmed,
27
+ onSelect
28
+ );
29
+ return /* @__PURE__ */ jsxs(
30
+ Combobox.Root,
31
+ {
32
+ collection,
33
+ onInputValueChange: (e) => {
34
+ handleInputChange(e);
35
+ },
36
+ onValueChange: (e) => {
37
+ handleSelectChange(e);
38
+ },
39
+ inputValue: input,
40
+ value: selectedOption?.value ? [selectedOption.value] : [],
41
+ className: "search-combobox-component",
42
+ "aria-label": intl.formatMessage({ id: "ariaLabel.search" }),
43
+ placeholder: placeholder ?? intl.formatMessage({ id: "searchPlaceholder" }),
44
+ openOnClick: input.length > 0,
45
+ closeOnSelect: true,
46
+ lazyMount: true,
47
+ unmountOnExit: true,
48
+ selectionBehavior: "preserve",
49
+ children: [
50
+ /* @__PURE__ */ jsx(AccessibleBoxHelper, { search: results }),
51
+ /* @__PURE__ */ jsxs(Combobox.Control, { children: [
52
+ /* @__PURE__ */ jsx(
53
+ InputGroup,
54
+ {
55
+ startElement: /* @__PURE__ */ jsx(Icon, { className: "search-icon", size: "md", children: /* @__PURE__ */ jsx(LuSearch, {}) }),
56
+ children: /* @__PURE__ */ jsx(Combobox.Input, { ref: controlRef })
57
+ }
58
+ ),
59
+ /* @__PURE__ */ jsx(Combobox.IndicatorGroup, { children: results.kind === "loading" ? /* @__PURE__ */ jsx(Spinner, { size: "xs", borderWidth: "1px" }) : input.length ? /* @__PURE__ */ jsx(
60
+ ClearIndicator,
61
+ {
62
+ clearValue: () => {
63
+ onInputChanged("");
64
+ onClear?.({ trigger: "user" });
65
+ controlRef.current?.focus();
66
+ }
67
+ }
68
+ ) : null })
69
+ ] }),
70
+ /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(Combobox.Positioner, { children: /* @__PURE__ */ jsx(SearchResults, { collection, input, results }) }) })
71
+ ]
72
+ }
73
+ );
74
+ }
75
+ const AccessibleBoxHelper = memo(function AccessibleBoxHelper2(props) {
76
+ const { search } = props;
77
+ const intl = useIntl();
78
+ let content;
79
+ if (search.kind === "loading") {
80
+ content = intl.formatMessage({ id: "loadingText" });
81
+ } else if (search.kind === "ready") {
82
+ content = intl.formatMessage({ id: "resultLoaded" });
83
+ }
84
+ return /* @__PURE__ */ jsx(VisuallyHidden, { "aria-live": "polite", children: content });
85
+ });
86
+ const ClearIndicator = memo(function ClearIndicator2(props) {
87
+ const intl = useIntl();
88
+ const clearButtonLabel = intl.formatMessage({
89
+ id: "ariaLabel.clearButton"
90
+ });
91
+ const clickHandler = (e) => {
92
+ e.preventDefault();
93
+ e.stopPropagation();
94
+ props.clearValue();
95
+ };
96
+ return /* @__PURE__ */ jsx(Tooltip, { content: clearButtonLabel, children: /* @__PURE__ */ jsx(
97
+ CloseButton,
98
+ {
99
+ variant: "ghost",
100
+ mr: "-10px",
101
+ size: "sm",
102
+ "aria-label": clearButtonLabel,
103
+ onClick: clickHandler,
104
+ onTouchEnd: clickHandler
105
+ }
106
+ ) });
107
+ });
108
+ function useSearchCollection(results) {
109
+ return useMemo(() => {
110
+ if (results.kind === "ready") {
111
+ const options = results.results.flatMap((group) => group.options);
112
+ return createListCollection({
113
+ items: options,
114
+ groupBy: (item) => item.group.id,
115
+ itemToString: (item) => item?.label || "",
116
+ itemToValue: (item) => item?.value || ""
117
+ });
118
+ }
119
+ return createListCollection({ items: [] });
120
+ }, [results]);
121
+ }
122
+ function useSearchHandlers(onInputChanged, onOptionConfirmed, onSelect) {
123
+ const handleInputChange = useEvent((e) => {
124
+ if (e.reason === "input-change" || e.reason === "interact-outside") {
125
+ onInputChanged(e.inputValue);
126
+ }
127
+ });
128
+ const handleSelectChange = useEvent((e) => {
129
+ const selectedItem = e.items.length ? e.items[0] : null;
130
+ if (!selectedItem) {
131
+ return;
132
+ }
133
+ onOptionConfirmed(selectedItem);
134
+ onSelect?.({
135
+ source: selectedItem.source,
136
+ result: selectedItem.result
137
+ });
138
+ });
139
+ return { handleInputChange, handleSelectChange };
140
+ }
141
+
142
+ export { SearchInput };
143
+ //# sourceMappingURL=SearchInput.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SearchInput.js","sources":["SearchInput.tsx"],"sourcesContent":["// SPDX-FileCopyrightText: 2023-2025 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\nimport {\n CloseButton,\n Combobox,\n ComboboxInputValueChangeDetails,\n ComboboxValueChangeDetails,\n createListCollection,\n Icon,\n InputGroup,\n Portal,\n Spinner,\n VisuallyHidden\n} from \"@chakra-ui/react\";\nimport { Tooltip } from \"@open-pioneer/chakra-snippets/tooltip\";\nimport { useEvent } from \"@open-pioneer/react-utils\";\nimport { useIntl } from \"open-pioneer:react-hooks\";\nimport { memo, UIEvent, useMemo, useRef } from \"react\";\nimport { LuSearch } from \"react-icons/lu\";\nimport { SearchClearEvent, SearchSelectEvent } from \"../api\";\nimport { SearchResults } from \"./SearchResults\";\nimport { SearchOption, SearchResultsState } from \"./useSearchState\";\n\nexport interface SearchInputProps {\n input: string;\n selectedOption?: SearchOption;\n results: SearchResultsState;\n placeholder?: string;\n\n onClear?: (event: SearchClearEvent) => void;\n onSelect?: (event: SearchSelectEvent) => void;\n onInputChanged: (newInput: string) => void;\n onOptionConfirmed: (option: SearchOption) => void;\n}\n\nexport function SearchInput(props: SearchInputProps) {\n const {\n input,\n selectedOption,\n results,\n placeholder,\n onClear,\n onSelect,\n onInputChanged,\n onOptionConfirmed\n } = props;\n const intl = useIntl();\n const controlRef = useRef<HTMLInputElement>(null);\n\n // Create the collection for the combobox and keep it synced with search results.\n const collection = useSearchCollection(results);\n\n // Event hooks for handling input changes and selecting options\n const { handleInputChange, handleSelectChange } = useSearchHandlers(\n onInputChanged,\n onOptionConfirmed,\n onSelect\n );\n\n return (\n <Combobox.Root\n collection={collection}\n onInputValueChange={(e) => {\n handleInputChange(e);\n }}\n onValueChange={(e) => {\n handleSelectChange(e);\n }}\n inputValue={input}\n value={selectedOption?.value ? [selectedOption.value] : []}\n className=\"search-combobox-component\"\n aria-label={intl.formatMessage({ id: \"ariaLabel.search\" })}\n placeholder={placeholder ?? intl.formatMessage({ id: \"searchPlaceholder\" })}\n openOnClick={input.length > 0}\n closeOnSelect={true}\n lazyMount={true}\n unmountOnExit={true}\n selectionBehavior=\"preserve\"\n >\n <AccessibleBoxHelper search={results} />\n <Combobox.Control>\n <InputGroup\n startElement={\n <Icon className={\"search-icon\"} size=\"md\">\n <LuSearch />\n </Icon>\n }\n >\n <Combobox.Input ref={controlRef} />\n </InputGroup>\n <Combobox.IndicatorGroup>\n {results.kind === \"loading\" ? (\n <Spinner size=\"xs\" borderWidth=\"1px\" />\n ) : input.length ? (\n <ClearIndicator\n clearValue={() => {\n onInputChanged(\"\");\n onClear?.({ trigger: \"user\" });\n controlRef.current?.focus();\n }}\n />\n ) : null}\n </Combobox.IndicatorGroup>\n </Combobox.Control>\n\n <Portal>\n <Combobox.Positioner>\n <SearchResults collection={collection} input={input} results={results} />\n </Combobox.Positioner>\n </Portal>\n </Combobox.Root>\n );\n}\n\n/**\n * Report loading status for screen readers.\n */\nconst AccessibleBoxHelper = memo(function AccessibleBoxHelper(props: {\n search: SearchResultsState;\n}) {\n const { search } = props;\n const intl = useIntl();\n\n let content;\n if (search.kind === \"loading\") {\n content = intl.formatMessage({ id: \"loadingText\" });\n } else if (search.kind === \"ready\") {\n content = intl.formatMessage({ id: \"resultLoaded\" });\n }\n\n return <VisuallyHidden aria-live=\"polite\">{content}</VisuallyHidden>;\n});\n\nconst ClearIndicator = memo(function ClearIndicator(props: { clearValue: () => void }) {\n const intl = useIntl();\n const clearButtonLabel = intl.formatMessage({\n id: \"ariaLabel.clearButton\"\n });\n const clickHandler = (e: UIEvent) => {\n e.preventDefault();\n e.stopPropagation();\n props.clearValue();\n };\n\n return (\n <Tooltip content={clearButtonLabel}>\n <CloseButton\n variant=\"ghost\"\n mr=\"-10px\"\n size=\"sm\"\n aria-label={clearButtonLabel}\n onClick={clickHandler}\n onTouchEnd={clickHandler}\n />\n </Tooltip>\n );\n});\n\nfunction useSearchCollection(results: SearchResultsState) {\n return useMemo(() => {\n if (results.kind === \"ready\") {\n const options = results.results.flatMap((group) => group.options);\n return createListCollection({\n items: options,\n groupBy: (item) => item.group.id,\n itemToString: (item) => item?.label || \"\",\n itemToValue: (item) => item?.value || \"\"\n });\n }\n return createListCollection<SearchOption>({ items: [] });\n }, [results]);\n}\n\nfunction useSearchHandlers(\n onInputChanged: (newValue: string) => void,\n onOptionConfirmed: (option: SearchOption) => void,\n onSelect: ((event: SearchSelectEvent) => void) | undefined\n) {\n const handleInputChange = useEvent((e: ComboboxInputValueChangeDetails) => {\n // Only update the input if the user actually typed something.\n // This keeps the input content if the user focuses another element or if the menu is closed.\n if (e.reason === \"input-change\" || e.reason === \"interact-outside\") {\n onInputChanged(e.inputValue);\n }\n });\n\n const handleSelectChange = useEvent((e: ComboboxValueChangeDetails<SearchOption>) => {\n const selectedItem = e.items.length ? e.items[0] : null;\n if (!selectedItem) {\n return;\n }\n onOptionConfirmed(selectedItem);\n onSelect?.({\n source: selectedItem.source,\n result: selectedItem.result\n });\n });\n\n return { handleInputChange, handleSelectChange };\n}\n"],"names":["AccessibleBoxHelper","ClearIndicator"],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import { ListCollection } from "@chakra-ui/react";
2
+ import { SearchOption, SearchResultsState } from "./useSearchState";
3
+ export interface SearchResultsProps {
4
+ collection: ListCollection<SearchOption>;
5
+ input: string;
6
+ results: SearchResultsState;
7
+ }
8
+ export declare const SearchResults: import("react").NamedExoticComponent<SearchResultsProps>;
@@ -0,0 +1,72 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { Combobox, Highlight, HStack, Span } from '@chakra-ui/react';
3
+ import { memo } from 'react';
4
+ import { useIntl } from '../_virtual/hooks.js';
5
+
6
+ const SearchResults = memo(function SearchResults2(props) {
7
+ const { collection, input, results } = props;
8
+ return /* @__PURE__ */ jsxs(
9
+ Combobox.Content,
10
+ {
11
+ className: "search-component-menu",
12
+ minW: "sm",
13
+ maxH: "300px",
14
+ overflowY: "auto",
15
+ overflowX: "hidden",
16
+ visibility: input.length ? "visible" : "hidden",
17
+ children: [
18
+ /* @__PURE__ */ jsx(FallbackContent, { results }),
19
+ /* @__PURE__ */ jsx(SearchResultList, { collection, input, results })
20
+ ]
21
+ }
22
+ );
23
+ });
24
+ const SearchResultList = memo(function SearchResults3(props) {
25
+ const { collection, input, results } = props;
26
+ return collection.group().map(([groupId, groupOptions], key) => {
27
+ return /* @__PURE__ */ jsxs(Combobox.ItemGroup, { children: [
28
+ /* @__PURE__ */ jsx(
29
+ Combobox.ItemGroupLabel,
30
+ {
31
+ backgroundColor: "colorPalette.100",
32
+ visibility: results.kind === "loading" ? "hidden" : "visible",
33
+ children: groupOptions[0]?.group.label
34
+ },
35
+ groupId
36
+ ),
37
+ groupOptions.map((searchResult, key2) => {
38
+ return /* @__PURE__ */ jsx(
39
+ Combobox.Item,
40
+ {
41
+ item: searchResult,
42
+ css: {
43
+ _checked: {
44
+ backgroundColor: "colorPalette.500",
45
+ color: "white"
46
+ },
47
+ "&:hover:not([data-state=checked])": {
48
+ backgroundColor: "colorPalette.50"
49
+ }
50
+ },
51
+ children: /* @__PURE__ */ jsx(Combobox.ItemText, { children: /* @__PURE__ */ jsx(Highlight, { ignoreCase: true, query: input, styles: { fontWeight: "bold" }, children: searchResult.label }) })
52
+ },
53
+ key2
54
+ );
55
+ })
56
+ ] }, key);
57
+ });
58
+ });
59
+ const FallbackContent = memo(function FallbackContent2(props) {
60
+ const { results } = props;
61
+ const intl = useIntl();
62
+ let content;
63
+ if (results.kind === "loading") {
64
+ content = intl.formatMessage({ id: "loadingText" });
65
+ } else {
66
+ content = intl.formatMessage({ id: "noOptionsText" });
67
+ }
68
+ return /* @__PURE__ */ jsx(Combobox.Empty, { padding: "0", children: /* @__PURE__ */ jsx(HStack, { p: "2", justifyContent: "center", children: /* @__PURE__ */ jsx(Span, { children: content }) }) });
69
+ });
70
+
71
+ export { SearchResults };
72
+ //# sourceMappingURL=SearchResults.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SearchResults.js","sources":["SearchResults.tsx"],"sourcesContent":["// SPDX-FileCopyrightText: 2023-2025 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\nimport { Combobox, Highlight, HStack, ListCollection, Span } from \"@chakra-ui/react\";\nimport { memo } from \"react\";\nimport { SearchOption, SearchResultsState } from \"./useSearchState\";\nimport { useIntl } from \"open-pioneer:react-hooks\";\n\nexport interface SearchResultsProps {\n collection: ListCollection<SearchOption>;\n input: string;\n results: SearchResultsState;\n}\n\nexport const SearchResults = memo(function SearchResults(props: SearchResultsProps) {\n const { collection, input, results } = props;\n return (\n <Combobox.Content\n className=\"search-component-menu\"\n minW=\"sm\"\n maxH=\"300px\"\n overflowY=\"auto\"\n overflowX=\"hidden\"\n visibility={input.length ? \"visible\" : \"hidden\"}\n >\n <FallbackContent results={results} />\n <SearchResultList collection={collection} input={input} results={results} />\n </Combobox.Content>\n );\n});\n\nconst SearchResultList = memo(function SearchResults(props: SearchResultsProps) {\n const { collection, input, results } = props;\n return collection.group().map(([groupId, groupOptions], key) => {\n return (\n <Combobox.ItemGroup key={key}>\n <Combobox.ItemGroupLabel\n key={groupId}\n backgroundColor=\"colorPalette.100\"\n visibility={results.kind === \"loading\" ? \"hidden\" : \"visible\"}\n >\n {groupOptions[0]?.group.label}\n </Combobox.ItemGroupLabel>\n {groupOptions.map((searchResult, key) => {\n return (\n <Combobox.Item\n key={key}\n item={searchResult}\n css={{\n _checked: {\n backgroundColor: \"colorPalette.500\",\n color: \"white\"\n },\n \"&:hover:not([data-state=checked])\": {\n backgroundColor: \"colorPalette.50\"\n }\n }}\n >\n <Combobox.ItemText>\n <Highlight ignoreCase query={input} styles={{ fontWeight: \"bold\" }}>\n {searchResult.label}\n </Highlight>\n </Combobox.ItemText>\n </Combobox.Item>\n );\n })}\n </Combobox.ItemGroup>\n );\n });\n});\n\n/**\n * Show loading label or fallback message when no results are found.\n */\nconst FallbackContent = memo(function FallbackContent(props: { results: SearchResultsState }) {\n const { results } = props;\n const intl = useIntl();\n\n let content;\n if (results.kind === \"loading\") {\n content = intl.formatMessage({ id: \"loadingText\" });\n } else {\n content = intl.formatMessage({ id: \"noOptionsText\" });\n }\n\n return (\n <Combobox.Empty padding=\"0\">\n <HStack p=\"2\" justifyContent=\"center\">\n <Span>{content}</Span>\n </HStack>\n </Combobox.Empty>\n );\n});\n"],"names":["SearchResults","key","FallbackContent"],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ import { MapModel } from "@open-pioneer/map";
2
+ import { SearchResult, SearchSource } from "../api";
3
+ export interface SearchOptionGroup {
4
+ /** Unique id of this group. */
5
+ id: string;
6
+ /** Display text shown in menu. */
7
+ label: string;
8
+ /** Set of options that belong to this group. */
9
+ options: SearchOption[];
10
+ }
11
+ export interface SearchOption {
12
+ /** Unique value for this option. */
13
+ value: string;
14
+ /** Display text shown in menu. */
15
+ label: string;
16
+ /** Search source that returned the suggestion. */
17
+ source: SearchSource;
18
+ /** Links to the parent group. */
19
+ group: SearchOptionGroup;
20
+ /** The raw result from the search source. */
21
+ result: SearchResult;
22
+ }
23
+ export interface SearchState {
24
+ input: string;
25
+ results: SearchResultsState;
26
+ selectedOption: SearchOption | undefined;
27
+ onOptionConfirmed: (newOption: SearchOption) => void;
28
+ onInputChanged: (newInput: string) => void;
29
+ }
30
+ export type SearchResultsReady = {
31
+ kind: "ready";
32
+ results: SearchOptionGroup[];
33
+ };
34
+ export type SearchResultsLoading = {
35
+ kind: "loading";
36
+ };
37
+ export type SearchResultsState = SearchResultsReady | SearchResultsLoading;
38
+ /**
39
+ * Keeps track of the current input text, active searches and their results.
40
+ *
41
+ * NOTE: it would be great to merge this state handling with the search controller
42
+ * in a future revision.
43
+ */
44
+ export declare function useSearchState(sources: SearchSource[], searchTypingDelay: number | undefined, maxResultsPerGroup: number | undefined, map: MapModel): SearchState;