@open-pioneer/search 1.5.0-dev.20260917081843 → 1.5.0

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.
Files changed (40) hide show
  1. package/CHANGELOG.md +33 -28
  2. package/README.md +5 -0
  3. package/_virtual/source-info.js +2 -2
  4. package/api.d.ts +10 -1
  5. package/model/SearchOperationState.d.ts +52 -0
  6. package/model/SearchOperationState.js +104 -0
  7. package/model/SearchOperationState.js.map +1 -0
  8. package/model/SearchViewModel.d.ts +76 -0
  9. package/model/SearchViewModel.js +197 -0
  10. package/model/SearchViewModel.js.map +1 -0
  11. package/model/index.d.ts +2 -0
  12. package/model/index.js +2 -0
  13. package/model/index.js.map +1 -0
  14. package/package.json +8 -6
  15. package/ui/Search.js +22 -41
  16. package/ui/Search.js.map +1 -1
  17. package/ui/SearchApiImpl.d.ts +6 -2
  18. package/ui/SearchApiImpl.js +7 -11
  19. package/ui/SearchApiImpl.js.map +1 -1
  20. package/ui/SearchInput.d.ts +2 -9
  21. package/ui/SearchInput.js +39 -66
  22. package/ui/SearchInput.js.map +1 -1
  23. package/ui/SearchResults.d.ts +3 -3
  24. package/ui/SearchResults.js +10 -11
  25. package/ui/SearchResults.js.map +1 -1
  26. package/ui/useComboboxCollection.d.ts +48 -0
  27. package/ui/useComboboxCollection.js +49 -0
  28. package/ui/useComboboxCollection.js.map +1 -0
  29. package/ui/useSearchSourceId.d.ts +9 -0
  30. package/ui/useSearchSourceId.js +23 -0
  31. package/ui/useSearchSourceId.js.map +1 -0
  32. package/ui/useSearchViewModel.d.ts +20 -0
  33. package/ui/useSearchViewModel.js +40 -0
  34. package/ui/useSearchViewModel.js.map +1 -0
  35. package/model/SearchController.d.ts +0 -21
  36. package/model/SearchController.js +0 -110
  37. package/model/SearchController.js.map +0 -1
  38. package/ui/useSearchState.d.ts +0 -46
  39. package/ui/useSearchState.js +0 -178
  40. package/ui/useSearchState.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,14 +1,19 @@
1
1
  # @open-pioneer/search
2
2
 
3
- ## 1.5.0-dev.20260917081843
3
+ ## 1.5.0
4
4
 
5
5
  ### Minor Changes
6
6
 
7
7
  - f790fda: Update to Chakra 3.37.0
8
+ - a186f5d: Update core packages to 4.8.0.
9
+ - 0cfbf39: Add support for an `id` property on the search source interface to identify sources. The property remains optional for the time being to preserve backwards compatibility.
8
10
 
9
11
  ### Patch Changes
10
12
 
11
13
  - 3203815: Update code to match new react linting rules from oxlint.
14
+ - 0cfbf39: Refactor the internal state management of the search component: the widget state now lives in a
15
+ long lived, reactive view model instead of react state. This is an internal change; the component's
16
+ properties, events and behavior are unchanged.
12
17
  - 09ec7c7: Updated dependencies
13
18
 
14
19
  ## 1.4.0
@@ -21,8 +26,8 @@
21
26
  The method returns the selected `SearchResult` (and its `SearchSource`) or `undefined` when no match is found.
22
27
  - 0b9143d: The `onSelect` callback now receives an additional property `trigger` (`"user"` or `"api-select"`) support different reactions based on the way a result was selected:
23
28
 
24
- - `user` indicates that event was triggered by the end user
25
- - `api-select` indicates that the selection was made via the API (i.e. `searchAndSelect`).
29
+ - `user` indicates that event was triggered by the end user
30
+ - `api-select` indicates that the selection was made via the API (i.e. `searchAndSelect`).
26
31
 
27
32
  ### Patch Changes
28
33
 
@@ -35,8 +40,8 @@
35
40
  - 9b5d5f3: Support for new common container props (role, aria-\*, data-\* and css)
36
41
  - d05a3b8: Refactor user interface: now based on chakra's combobox instead of react-select.
37
42
 
38
- NOTE: This causes changes to many internal CSS classes and attributes.
39
- The UX of the control should be the same.
43
+ NOTE: This causes changes to many internal CSS classes and attributes.
44
+ The UX of the control should be the same.
40
45
 
41
46
  - d54ccfd: Update to Chakra UI 3.35.0
42
47
  - 206b397: Update to trails core packages 4.6.0
@@ -73,8 +78,8 @@
73
78
 
74
79
  - 0485014: Introduce search API and `resetInput` method (see [PR](https://github.com/open-pioneer/trails-openlayers-base-packages/pull/493)).
75
80
 
76
- The search API allows programmatic access to the search component.
77
- Currently, it provides a method to reset the search input field.
81
+ The search API allows programmatic access to the search component.
82
+ Currently, it provides a method to reset the search input field.
78
83
 
79
84
  - 2732052: Icons have been changed to unify the appearance of the components. Preferably, Lucide react-icons are used.
80
85
  - 2abcaaf: Update to chakra-ui 3.28.0
@@ -97,16 +102,16 @@
97
102
 
98
103
  - 9376a74: Ensure that icons and other decorative elements are hidden from the screen reader using the `aria-hidden="true"` attribute.
99
104
 
100
- The easiest way to do that is to wrap icons into chakra's `<Icon />` component, for example:
105
+ The easiest way to do that is to wrap icons into chakra's `<Icon />` component, for example:
101
106
 
102
- ```tsx
103
- import { Icon } from "@chakra-ui/react";
104
- import { FiX } from "react-icons/fi";
107
+ ```tsx
108
+ import { Icon } from "@chakra-ui/react";
109
+ import { FiX } from "react-icons/fi";
105
110
 
106
- <Icon>
107
- <FiX />
108
- </Icon>;
109
- ```
111
+ <Icon>
112
+ <FiX />
113
+ </Icon>;
114
+ ```
110
115
 
111
116
  ## 0.10.0
112
117
 
@@ -115,17 +120,17 @@
115
120
  - 193068a: Deprecate the `mapId` property on React components.
116
121
  Use the `MapModel` directly instead to pass a reference to the map.
117
122
 
118
- Example:
123
+ Example:
119
124
 
120
- ```tsx
121
- // Default map for entire component tree
122
- <DefaultMapProvider map={mapModel}>
123
- <Toc />
124
- </DefaultMapProvider>
125
+ ```tsx
126
+ // Default map for entire component tree
127
+ <DefaultMapProvider map={mapModel}>
128
+ <Toc />
129
+ </DefaultMapProvider>
125
130
 
126
- // Map for specific component
127
- <Toc map={mapModel} />
128
- ```
131
+ // Map for specific component
132
+ <Toc map={mapModel} />
133
+ ```
129
134
 
130
135
  ### Patch Changes
131
136
 
@@ -149,9 +154,9 @@
149
154
  ### Minor Changes
150
155
 
151
156
  - 2fa8020: Update trails core package dependencies.
152
- - Also updates Chakra UI to the latest 2.x version and Chakra React Select to version 5.
153
- - Removes any obsolete references to `@chakra-ui/system`.
154
- This dependency seems to be no longer required and may lead to duplicate packages in your dependency tree.
157
+ - Also updates Chakra UI to the latest 2.x version and Chakra React Select to version 5.
158
+ - Removes any obsolete references to `@chakra-ui/system`.
159
+ This dependency seems to be no longer required and may lead to duplicate packages in your dependency tree.
155
160
 
156
161
  ### Patch Changes
157
162
 
@@ -170,7 +175,7 @@
170
175
  - 583f1d6: The `mapId` or `map` properties are now optional on individual components.
171
176
  You can use the `DefaultMapProvider` to configure an implicit default value.
172
177
 
173
- Note that configuring _neither_ a default _nor_ an explicit `map` or `mapId` will trigger a runtime error.
178
+ Note that configuring _neither_ a default _nor_ an explicit `map` or `mapId` will trigger a runtime error.
174
179
 
175
180
  - 583f1d6: All UI components in this project now accept the `mapId` (a `string`) _or_ the `map` (a `MapModel`) directly.
176
181
  - a8b3449: Switch to a new versioning strategy.
package/README.md CHANGED
@@ -174,6 +174,11 @@ import { Search, SearchSource, SearchResult } from "@open-pioneer/search";
174
174
  import { MAP_ID } from "./MapConfigProviderImpl";
175
175
 
176
176
  class MySearchSource implements SearchSource {
177
+ // The optional id of this source. It must be unique among the sources used by the same
178
+ // search component and it must not change. If no id is defined, the search component
179
+ // generates one internally.
180
+ id = "my-sample-rest-service";
181
+
177
182
  // The label of this source, used as a title for this source's results.
178
183
  label = "My sample REST-Service";
179
184
 
@@ -1,6 +1,6 @@
1
- const sourceId$1 = "@open-pioneer/search/ui/useSearchState";
1
+ const sourceId$1 = "@open-pioneer/search/model/SearchViewModel";
2
2
 
3
- const sourceId = "@open-pioneer/search/model/SearchController";
3
+ const sourceId = "@open-pioneer/search/model/SearchOperationState";
4
4
 
5
5
  export { sourceId, sourceId$1 };
6
6
  //# sourceMappingURL=source-info.js.map
package/api.d.ts CHANGED
@@ -6,6 +6,15 @@ import { Projection } from "ol/proj";
6
6
  * Developers can create classes that implement this interface for different search sources.
7
7
  */
8
8
  export interface SearchSource {
9
+ /**
10
+ * The id of this source.
11
+ *
12
+ * Must be unique across all search sources used within the same search component.
13
+ *
14
+ * The id must not change while the source is in use (this value is not reactive).
15
+ * If no id is defined, the search component generates one internally.
16
+ */
17
+ readonly id?: string;
9
18
  /**
10
19
  * The label of this source.
11
20
  *
@@ -124,7 +133,7 @@ export interface SearchApi {
124
133
  resetInput(): void;
125
134
  /**
126
135
  * Sets the search input field to the given value.
127
- * The search is not automatically triggered when setting the input value.
136
+ * The suggestion menu is not opened automatically when setting the input value.
128
137
  * @param inputValue The value to be set in the search input field.
129
138
  */
130
139
  setInputValue(inputValue: string): void;
@@ -0,0 +1,52 @@
1
+ import { MapModel } from "@open-pioneer/map";
2
+ import { SearchResult, SearchSource } from "../api";
3
+ /**
4
+ * The state of a single search operation, with one entry per search source.
5
+ */
6
+ export interface SearchOperationState {
7
+ /** The query string used for this search operation. */
8
+ readonly query: string;
9
+ /** True while the search is being delayed or executed. */
10
+ readonly pending: boolean;
11
+ /** Results per search source. Empty while {@link pending}. */
12
+ readonly sourceResults: readonly SearchSourceResults[];
13
+ }
14
+ /**
15
+ * Search results of a single search source.
16
+ */
17
+ export interface SearchSourceResults {
18
+ /** The source that returned the {@link results}. */
19
+ readonly source: SearchSource;
20
+ /** The results returned by the source. */
21
+ readonly results: readonly SearchResult[];
22
+ }
23
+ /** The state before anything has been searched for. */
24
+ export declare const EMPTY_STATE: SearchOperationState;
25
+ export interface SearchOperationOptions {
26
+ query: string;
27
+ sources: readonly SearchSource[];
28
+ maxResults: number;
29
+ map: MapModel;
30
+ typingDelay: number;
31
+ }
32
+ /**
33
+ * A single search operation: wait for the typing delay, then query all sources in parallel.
34
+ *
35
+ * The operation can be cancelled by calling {@link destroy}.
36
+ */
37
+ export declare class SearchOperationStateImpl implements SearchOperationState {
38
+ #private;
39
+ readonly query: string;
40
+ /** Resolves when the results are available; rejects if the operation was cancelled. */
41
+ readonly completed: Promise<void>;
42
+ constructor(options: SearchOperationOptions);
43
+ /**
44
+ * Cancels this operation.
45
+ *
46
+ * The operation also stops being {@link pending}: it will never deliver any results, so
47
+ * anything that still shows this state must not keep waiting for them.
48
+ */
49
+ destroy(): void;
50
+ get pending(): boolean;
51
+ get sourceResults(): readonly SearchSourceResults[];
52
+ }
@@ -0,0 +1,104 @@
1
+ import { reactive, batch } from '@conterra/reactivity-core';
2
+ import { createLogger, isAbortError, createAbortError } from '@open-pioneer/core';
3
+ import { sourceId } from '../_virtual/source-info.js';
4
+
5
+ const LOG = createLogger(sourceId);
6
+ const EMPTY_STATE = {
7
+ query: "",
8
+ pending: false,
9
+ sourceResults: []
10
+ };
11
+ class SearchOperationStateImpl {
12
+ #options;
13
+ query;
14
+ /** Resolves when the results are available; rejects if the operation was cancelled. */
15
+ completed;
16
+ #pending = reactive(true);
17
+ #sourceResults = reactive([]);
18
+ #abortController = new AbortController();
19
+ constructor(options) {
20
+ this.#options = options;
21
+ this.query = options.query;
22
+ if (!options.query) {
23
+ this.#pending.value = false;
24
+ this.completed = Promise.resolve();
25
+ } else {
26
+ this.completed = this.#run();
27
+ }
28
+ }
29
+ /**
30
+ * Cancels this operation.
31
+ *
32
+ * The operation also stops being {@link pending}: it will never deliver any results, so
33
+ * anything that still shows this state must not keep waiting for them.
34
+ */
35
+ destroy() {
36
+ this.#abortController.abort();
37
+ this.#pending.value = false;
38
+ }
39
+ get pending() {
40
+ return this.#pending.value;
41
+ }
42
+ get sourceResults() {
43
+ return this.#sourceResults.value;
44
+ }
45
+ async #run() {
46
+ const { query, sources, maxResults, map, typingDelay } = this.#options;
47
+ const signal = this.#abortController.signal;
48
+ LOG.debug(`starting search operation for query '${query}'`);
49
+ await waitForTimeout(typingDelay, signal);
50
+ const sourceResults = await Promise.all(
51
+ sources.map(async (source) => ({
52
+ source,
53
+ results: await this.#searchInSource(source, { query, maxResults, map }, signal)
54
+ }))
55
+ );
56
+ signal.throwIfAborted();
57
+ batch(() => {
58
+ this.#sourceResults.value = sourceResults;
59
+ this.#pending.value = false;
60
+ });
61
+ LOG.debug(`search operation for query '${query}' finished`, sourceResults);
62
+ }
63
+ async #searchInSource(source, options, signal) {
64
+ const { query, maxResults, map } = options;
65
+ try {
66
+ LOG.debug(`starting search on source '${source.label}'`);
67
+ let results = await source.search(query, {
68
+ maxResults,
69
+ signal,
70
+ map,
71
+ mapProjection: map.projection
72
+ // for backwards compat
73
+ });
74
+ signal.throwIfAborted();
75
+ if (results.length > maxResults) {
76
+ results = results.slice(0, maxResults);
77
+ }
78
+ LOG.debug(`finished search on source '${source.label}'`, results);
79
+ return results;
80
+ } catch (e) {
81
+ if (!isAbortError(e)) {
82
+ LOG.error(`search on source '${source.label}' failed`, e);
83
+ }
84
+ return [];
85
+ }
86
+ }
87
+ }
88
+ async function waitForTimeout(timeoutMillis, signal) {
89
+ signal.throwIfAborted();
90
+ await new Promise((resolve, reject) => {
91
+ const onAbort = () => {
92
+ clearTimeout(timeoutId);
93
+ reject(createAbortError());
94
+ };
95
+ const timeoutId = setTimeout(() => {
96
+ signal.removeEventListener("abort", onAbort);
97
+ resolve();
98
+ }, timeoutMillis);
99
+ signal.addEventListener("abort", onAbort, { once: true });
100
+ });
101
+ }
102
+
103
+ export { EMPTY_STATE, SearchOperationStateImpl };
104
+ //# sourceMappingURL=SearchOperationState.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SearchOperationState.js","sources":["SearchOperationState.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2023-2025 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\n\nimport { batch, reactive } from \"@conterra/reactivity-core\";\nimport { createAbortError, createLogger, isAbortError } 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 * The state of a single search operation, with one entry per search source.\n */\nexport interface SearchOperationState {\n // NOTE: `error` state is not modelled here; it might not be needed.\n // Failed searches simply return no results for the time being.\n\n /** The query string used for this search operation. */\n readonly query: string;\n\n /** True while the search is being delayed or executed. */\n readonly pending: boolean;\n\n /** Results per search source. Empty while {@link pending}. */\n readonly sourceResults: readonly SearchSourceResults[];\n}\n\n/**\n * Search results of a single search source.\n */\nexport interface SearchSourceResults {\n /** The source that returned the {@link results}. */\n readonly source: SearchSource;\n\n /** The results returned by the source. */\n readonly results: readonly SearchResult[];\n}\n\n/** The state before anything has been searched for. */\nexport const EMPTY_STATE: SearchOperationState = {\n query: \"\",\n pending: false,\n sourceResults: []\n};\n\nexport interface SearchOperationOptions {\n query: string;\n sources: readonly SearchSource[];\n maxResults: number;\n map: MapModel;\n typingDelay: number;\n}\n\n/**\n * A single search operation: wait for the typing delay, then query all sources in parallel.\n *\n * The operation can be cancelled by calling {@link destroy}.\n */\nexport class SearchOperationStateImpl implements SearchOperationState {\n #options: SearchOperationOptions;\n\n readonly query: string;\n\n /** Resolves when the results are available; rejects if the operation was cancelled. */\n readonly completed: Promise<void>;\n\n #pending = reactive(true);\n #sourceResults = reactive<readonly SearchSourceResults[]>([]);\n #abortController = new AbortController();\n\n constructor(options: SearchOperationOptions) {\n this.#options = options;\n this.query = options.query;\n if (!options.query) {\n this.#pending.value = false;\n this.completed = Promise.resolve();\n } else {\n this.completed = this.#run();\n }\n }\n\n /**\n * Cancels this operation.\n *\n * The operation also stops being {@link pending}: it will never deliver any results, so\n * anything that still shows this state must not keep waiting for them.\n */\n destroy(): void {\n this.#abortController.abort();\n this.#pending.value = false;\n }\n\n get pending(): boolean {\n return this.#pending.value;\n }\n\n get sourceResults(): readonly SearchSourceResults[] {\n return this.#sourceResults.value;\n }\n\n async #run(): Promise<void> {\n const { query, sources, maxResults, map, typingDelay } = this.#options;\n const signal = this.#abortController.signal;\n LOG.debug(`starting search operation for query '${query}'`);\n\n // The user might still be typing: if the search term changes, we get cancelled here.\n await waitForTimeout(typingDelay, signal);\n\n // Search all sources in parallel. searchInSource() does not throw.\n const sourceResults = await Promise.all(\n sources.map(async (source) => ({\n source,\n results: await this.#searchInSource(source, { query, maxResults, map }, signal)\n }))\n );\n signal.throwIfAborted();\n\n batch(() => {\n this.#sourceResults.value = sourceResults;\n this.#pending.value = false;\n });\n LOG.debug(`search operation for query '${query}' finished`, sourceResults);\n }\n\n async #searchInSource(\n source: SearchSource,\n options: Pick<SearchOperationOptions, \"query\" | \"maxResults\" | \"map\">,\n signal: AbortSignal\n ): Promise<SearchResult[]> {\n const { query, maxResults, map } = options;\n try {\n LOG.debug(`starting search on source '${source.label}'`);\n\n let results = await source.search(query, {\n maxResults,\n signal,\n map,\n mapProjection: map.projection // for backwards compat\n });\n signal.throwIfAborted();\n\n if (results.length > maxResults) {\n results = results.slice(0, maxResults);\n }\n\n LOG.debug(`finished search on source '${source.label}'`, results);\n return results;\n } catch (e) {\n if (!isAbortError(e)) {\n LOG.error(`search on source '${source.label}' failed`, e);\n }\n return [];\n }\n }\n}\n\n/**\n * Waits for timeoutMillis or until signal is aborted, whatever happens first.\n */\nasync function waitForTimeout(timeoutMillis: number, signal: AbortSignal): Promise<void> {\n signal.throwIfAborted();\n\n await new Promise<void>((resolve, reject) => {\n const onAbort = () => {\n clearTimeout(timeoutId);\n reject(createAbortError());\n };\n const timeoutId = setTimeout(() => {\n // NOTE: If the signal had been aborted, onAbort() would have cleared this timeout.\n signal.removeEventListener(\"abort\", onAbort);\n resolve();\n }, timeoutMillis);\n signal.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ import { MapModel } from "@open-pioneer/map";
2
+ import { SearchClearEvent, SearchClearTrigger, SearchResult, SearchSelectEvent, SearchSelectTrigger, SearchSource, SelectResult } from "../api";
3
+ import { SearchOperationState } from "./SearchOperationState";
4
+ /**
5
+ * Options to construct a {@link SearchViewModel}.
6
+ */
7
+ export interface SearchViewModelOptions {
8
+ /** The map the search is associated with. It is passed on to the search sources. */
9
+ map: MapModel;
10
+ /** Called whenever a result has been selected (by the user or via the search API). */
11
+ onSelect: (event: SearchSelectEvent) => void;
12
+ /** Called whenever the search has been cleared (by the user or via the search API). */
13
+ onClear: (event: SearchClearEvent) => void;
14
+ }
15
+ /**
16
+ * The view model holds the state for the search component.
17
+ *
18
+ * A new search operation is started automatically whenever the search term changes.
19
+ */
20
+ export declare class SearchViewModel {
21
+ #private;
22
+ constructor(options: SearchViewModelOptions);
23
+ destroy(): void;
24
+ get sources(): SearchSource[];
25
+ set sources(newSources: SearchSource[]);
26
+ /**
27
+ * Debounce delay (in milliseconds) that controls how long the view model waits
28
+ * before starting a new search after the search term changed.
29
+ */
30
+ get searchTypingDelay(): number;
31
+ set searchTypingDelay(value: number | undefined);
32
+ /**
33
+ * Maximum number of results per search source.
34
+ */
35
+ get maxResultsPerSource(): number;
36
+ set maxResultsPerSource(value: number | undefined);
37
+ /**
38
+ * The text shown in the search control's input field.
39
+ *
40
+ * Use {@link setInputValue} to change it.
41
+ */
42
+ get inputValue(): string;
43
+ /**
44
+ * The result currently selected by the user, if any.
45
+ */
46
+ get selectedResult(): SelectResult | undefined;
47
+ /**
48
+ * True while a search is being delayed or executed.
49
+ */
50
+ get pending(): boolean;
51
+ /**
52
+ * The state of the current search operation.
53
+ *
54
+ * The reactive properties of this object update as the search progresses.
55
+ */
56
+ get currentSearchState(): SearchOperationState;
57
+ /**
58
+ * Replaces the text in the input field and searches for it.
59
+ */
60
+ setInputValue(value: string): void;
61
+ /**
62
+ * Confirms the given result: the input field shows the result's label and the result
63
+ * becomes the current selection.
64
+ */
65
+ selectResult(source: SearchSource, result: SearchResult, trigger: SearchSelectTrigger): void;
66
+ /**
67
+ * Clears the input field, the current selection and the current search.
68
+ */
69
+ clear(trigger: SearchClearTrigger): void;
70
+ /**
71
+ * Searches for the given query and selects the first matching result.
72
+ *
73
+ * Returns the selected result, or `undefined` if the search did not return anything.
74
+ */
75
+ searchAndSelect(query: string): Promise<SelectResult | undefined>;
76
+ }
@@ -0,0 +1,197 @@
1
+ import { reactive, watch, batch } from '@conterra/reactivity-core';
2
+ import { createLogger, shallowEqual, destroyResource, isAbortError } from '@open-pioneer/core';
3
+ import { sourceId$1 as sourceId } from '../_virtual/source-info.js';
4
+ import { EMPTY_STATE, SearchOperationStateImpl } from './SearchOperationState.js';
5
+
6
+ const LOG = createLogger(sourceId);
7
+ const DEFAULT_MAX_RESULTS_PER_SOURCE = 5;
8
+ const DEFAULT_SEARCH_TYPING_DELAY = 200;
9
+ class SearchViewModel {
10
+ #map;
11
+ #onSelect;
12
+ #onClear;
13
+ #maxResultsPerSource = reactive(DEFAULT_MAX_RESULTS_PER_SOURCE);
14
+ #searchTypingDelay = reactive(DEFAULT_SEARCH_TYPING_DELAY);
15
+ #sources = reactive([], { equal: shallowEqual });
16
+ /** The text currently shown in the input field. */
17
+ #inputValue = reactive("");
18
+ /**
19
+ * The term that searches are being run for.
20
+ *
21
+ * Not necessarily the same as {@link #inputValue}.
22
+ * Selecting a result changes the _input value_, but not this search term
23
+ * to avoid starting an additional search.
24
+ */
25
+ #searchTerm = reactive("");
26
+ /** The result selected by the user or via the search API. */
27
+ #selected = reactive();
28
+ /** The state of the current search operation. */
29
+ #currentState = reactive(EMPTY_STATE);
30
+ /**
31
+ * The currently running search operation.
32
+ * Only one search can be running at a time; new search operations cancel old ones.
33
+ */
34
+ #currentOperation;
35
+ #searchWatch;
36
+ constructor(options) {
37
+ this.#map = options.map;
38
+ this.#onSelect = options.onSelect;
39
+ this.#onClear = options.onClear;
40
+ this.#searchWatch = watch(
41
+ () => [this.#searchTerm.value.trim()],
42
+ ([query]) => {
43
+ this.#currentState.value = this.#startSearchOperation(query);
44
+ }
45
+ );
46
+ }
47
+ destroy() {
48
+ this.#searchWatch = destroyResource(this.#searchWatch);
49
+ this.#currentOperation = destroyResource(this.#currentOperation);
50
+ }
51
+ get sources() {
52
+ return this.#sources.value;
53
+ }
54
+ set sources(newSources) {
55
+ this.#sources.value = newSources;
56
+ }
57
+ /**
58
+ * Debounce delay (in milliseconds) that controls how long the view model waits
59
+ * before starting a new search after the search term changed.
60
+ */
61
+ get searchTypingDelay() {
62
+ return this.#searchTypingDelay.value;
63
+ }
64
+ set searchTypingDelay(value) {
65
+ this.#searchTypingDelay.value = value ?? DEFAULT_SEARCH_TYPING_DELAY;
66
+ }
67
+ /**
68
+ * Maximum number of results per search source.
69
+ */
70
+ get maxResultsPerSource() {
71
+ return this.#maxResultsPerSource.value;
72
+ }
73
+ set maxResultsPerSource(value) {
74
+ this.#maxResultsPerSource.value = value ?? DEFAULT_MAX_RESULTS_PER_SOURCE;
75
+ }
76
+ /**
77
+ * The text shown in the search control's input field.
78
+ *
79
+ * Use {@link setInputValue} to change it.
80
+ */
81
+ get inputValue() {
82
+ return this.#inputValue.value;
83
+ }
84
+ /**
85
+ * The result currently selected by the user, if any.
86
+ */
87
+ get selectedResult() {
88
+ return this.#selected.value;
89
+ }
90
+ /**
91
+ * True while a search is being delayed or executed.
92
+ */
93
+ get pending() {
94
+ return this.currentSearchState.pending;
95
+ }
96
+ /**
97
+ * The state of the current search operation.
98
+ *
99
+ * The reactive properties of this object update as the search progresses.
100
+ */
101
+ get currentSearchState() {
102
+ return this.#currentState.value;
103
+ }
104
+ /**
105
+ * Replaces the text in the input field and searches for it.
106
+ */
107
+ setInputValue(value) {
108
+ batch(() => {
109
+ this.#inputValue.value = value;
110
+ this.#searchTerm.value = value;
111
+ this.#selected.value = void 0;
112
+ });
113
+ }
114
+ /**
115
+ * Confirms the given result: the input field shows the result's label and the result
116
+ * becomes the current selection.
117
+ */
118
+ selectResult(source, result, trigger) {
119
+ batch(() => {
120
+ this.#inputValue.value = result.label;
121
+ this.#selected.value = { source, result };
122
+ });
123
+ this.#onSelect({ source, result, trigger });
124
+ }
125
+ /**
126
+ * Clears the input field, the current selection and the current search.
127
+ */
128
+ clear(trigger) {
129
+ batch(() => {
130
+ this.#inputValue.value = "";
131
+ this.#searchTerm.value = "";
132
+ this.#selected.value = void 0;
133
+ });
134
+ this.#onClear({ trigger });
135
+ }
136
+ /**
137
+ * Searches for the given query and selects the first matching result.
138
+ *
139
+ * Returns the selected result, or `undefined` if the search did not return anything.
140
+ */
141
+ async searchAndSelect(query) {
142
+ const operation = this.#startSearchOperation(query.trim());
143
+ try {
144
+ await operation.completed;
145
+ } catch {
146
+ return void 0;
147
+ }
148
+ const selection = firstResult(operation.sourceResults);
149
+ if (!selection) {
150
+ return void 0;
151
+ }
152
+ batch(() => {
153
+ this.#currentState.value = operation;
154
+ this.#inputValue.value = selection.result.label;
155
+ this.#selected.value = selection;
156
+ });
157
+ this.#onSelect({ ...selection, trigger: "api-select" });
158
+ return selection;
159
+ }
160
+ /**
161
+ * Starts a new search operation, cancelling the one that may still be running.
162
+ *
163
+ * The current configuration is captured here, when the operation starts: it must not be
164
+ * re-read from the operation's async continuations.
165
+ *
166
+ * Errors are reported here, so callers never have to handle them (and cannot cause an
167
+ * unhandled rejection by ignoring the operation's promise).
168
+ */
169
+ #startSearchOperation(query) {
170
+ this.#currentOperation?.destroy();
171
+ const operation = this.#currentOperation = new SearchOperationStateImpl({
172
+ query,
173
+ sources: this.sources,
174
+ maxResults: this.maxResultsPerSource,
175
+ map: this.#map,
176
+ typingDelay: this.searchTypingDelay
177
+ });
178
+ operation.completed.catch((e) => {
179
+ if (!isAbortError(e)) {
180
+ LOG.error(`search operation for query '${query}' failed`, e);
181
+ }
182
+ });
183
+ return operation;
184
+ }
185
+ }
186
+ function firstResult(sourceResults) {
187
+ for (const { source, results } of sourceResults) {
188
+ const result = results[0];
189
+ if (result) {
190
+ return { source, result };
191
+ }
192
+ }
193
+ return void 0;
194
+ }
195
+
196
+ export { SearchViewModel };
197
+ //# sourceMappingURL=SearchViewModel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SearchViewModel.js","sources":["SearchViewModel.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2023-2025 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\n\nimport { batch, CleanupHandle, reactive, watch } from \"@conterra/reactivity-core\";\nimport { createLogger, destroyResource, isAbortError, shallowEqual } from \"@open-pioneer/core\";\nimport { MapModel } from \"@open-pioneer/map\";\nimport { sourceId } from \"open-pioneer:source-info\";\nimport {\n SearchClearEvent,\n SearchClearTrigger,\n SearchResult,\n SearchSelectEvent,\n SearchSelectTrigger,\n SearchSource,\n SelectResult\n} from \"../api\";\nimport {\n EMPTY_STATE,\n SearchOperationState,\n SearchOperationStateImpl,\n SearchSourceResults\n} from \"./SearchOperationState\";\n\nconst LOG = createLogger(sourceId);\n\nconst DEFAULT_MAX_RESULTS_PER_SOURCE = 5;\nconst DEFAULT_SEARCH_TYPING_DELAY = 200;\n\n/**\n * Options to construct a {@link SearchViewModel}.\n */\nexport interface SearchViewModelOptions {\n /** The map the search is associated with. It is passed on to the search sources. */\n map: MapModel;\n\n /** Called whenever a result has been selected (by the user or via the search API). */\n onSelect: (event: SearchSelectEvent) => void;\n\n /** Called whenever the search has been cleared (by the user or via the search API). */\n onClear: (event: SearchClearEvent) => void;\n}\n\n/**\n * The view model holds the state for the search component.\n *\n * A new search operation is started automatically whenever the search term changes.\n */\nexport class SearchViewModel {\n #map: MapModel;\n #onSelect: (event: SearchSelectEvent) => void;\n #onClear: (event: SearchClearEvent) => void;\n\n #maxResultsPerSource = reactive(DEFAULT_MAX_RESULTS_PER_SOURCE);\n #searchTypingDelay = reactive(DEFAULT_SEARCH_TYPING_DELAY);\n #sources = reactive<SearchSource[]>([], { equal: shallowEqual });\n\n /** The text currently shown in the input field. */\n #inputValue = reactive(\"\");\n\n /**\n * The term that searches are being run for.\n *\n * Not necessarily the same as {@link #inputValue}.\n * Selecting a result changes the _input value_, but not this search term\n * to avoid starting an additional search.\n */\n #searchTerm = reactive(\"\");\n\n /** The result selected by the user or via the search API. */\n #selected = reactive<SelectResult>();\n\n /** The state of the current search operation. */\n #currentState = reactive<SearchOperationState>(EMPTY_STATE);\n\n /**\n * The currently running search operation.\n * Only one search can be running at a time; new search operations cancel old ones.\n */\n #currentOperation: SearchOperationStateImpl | undefined;\n\n #searchWatch: CleanupHandle | undefined;\n\n constructor(options: SearchViewModelOptions) {\n this.#map = options.map;\n this.#onSelect = options.onSelect;\n this.#onClear = options.onClear;\n\n // Runs a new search whenever the search term changes.\n this.#searchWatch = watch(\n () => [this.#searchTerm.value.trim()],\n ([query]) => {\n this.#currentState.value = this.#startSearchOperation(query);\n }\n );\n }\n\n destroy(): void {\n this.#searchWatch = destroyResource(this.#searchWatch);\n this.#currentOperation = destroyResource(this.#currentOperation);\n }\n\n get sources(): SearchSource[] {\n return this.#sources.value;\n }\n\n set sources(newSources: SearchSource[]) {\n this.#sources.value = newSources;\n }\n\n /**\n * Debounce delay (in milliseconds) that controls how long the view model waits\n * before starting a new search after the search term changed.\n */\n get searchTypingDelay(): number {\n return this.#searchTypingDelay.value;\n }\n\n set searchTypingDelay(value: number | undefined) {\n this.#searchTypingDelay.value = value ?? DEFAULT_SEARCH_TYPING_DELAY;\n }\n\n /**\n * Maximum number of results per search source.\n */\n get maxResultsPerSource(): number {\n return this.#maxResultsPerSource.value;\n }\n\n set maxResultsPerSource(value: number | undefined) {\n this.#maxResultsPerSource.value = value ?? DEFAULT_MAX_RESULTS_PER_SOURCE;\n }\n\n /**\n * The text shown in the search control's input field.\n *\n * Use {@link setInputValue} to change it.\n */\n get inputValue(): string {\n return this.#inputValue.value;\n }\n\n /**\n * The result currently selected by the user, if any.\n */\n get selectedResult(): SelectResult | undefined {\n return this.#selected.value;\n }\n\n /**\n * True while a search is being delayed or executed.\n */\n get pending(): boolean {\n return this.currentSearchState.pending;\n }\n\n /**\n * The state of the current search operation.\n *\n * The reactive properties of this object update as the search progresses.\n */\n get currentSearchState(): SearchOperationState {\n return this.#currentState.value;\n }\n\n /**\n * Replaces the text in the input field and searches for it.\n */\n setInputValue(value: string): void {\n batch(() => {\n this.#inputValue.value = value;\n this.#searchTerm.value = value;\n this.#selected.value = undefined;\n });\n }\n\n /**\n * Confirms the given result: the input field shows the result's label and the result\n * becomes the current selection.\n */\n selectResult(source: SearchSource, result: SearchResult, trigger: SearchSelectTrigger): void {\n batch(() => {\n this.#inputValue.value = result.label;\n this.#selected.value = { source, result };\n });\n this.#onSelect({ source, result, trigger });\n }\n\n /**\n * Clears the input field, the current selection and the current search.\n */\n clear(trigger: SearchClearTrigger): void {\n batch(() => {\n this.#inputValue.value = \"\";\n this.#searchTerm.value = \"\";\n this.#selected.value = undefined;\n });\n this.#onClear({ trigger });\n }\n\n /**\n * Searches for the given query and selects the first matching result.\n *\n * Returns the selected result, or `undefined` if the search did not return anything.\n */\n async searchAndSelect(query: string): Promise<SelectResult | undefined> {\n // NOTE: The search term is left untouched, so this search does not change the input\n // field's state (and the input field does not search for this query again).\n const operation = this.#startSearchOperation(query.trim());\n try {\n await operation.completed;\n } catch {\n // Cancelled (another search was started) or failed; already reported by\n // #startSearchOperation.\n return undefined;\n }\n\n const selection = firstResult(operation.sourceResults);\n if (!selection) {\n return undefined;\n }\n\n batch(() => {\n this.#currentState.value = operation; // show the results of this search\n this.#inputValue.value = selection.result.label;\n this.#selected.value = selection;\n });\n this.#onSelect({ ...selection, trigger: \"api-select\" });\n return selection;\n }\n\n /**\n * Starts a new search operation, cancelling the one that may still be running.\n *\n * The current configuration is captured here, when the operation starts: it must not be\n * re-read from the operation's async continuations.\n *\n * Errors are reported here, so callers never have to handle them (and cannot cause an\n * unhandled rejection by ignoring the operation's promise).\n */\n #startSearchOperation(query: string): SearchOperationStateImpl {\n this.#currentOperation?.destroy();\n\n const operation = (this.#currentOperation = new SearchOperationStateImpl({\n query,\n sources: this.sources,\n maxResults: this.maxResultsPerSource,\n map: this.#map,\n typingDelay: this.searchTypingDelay\n }));\n operation.completed.catch((e) => {\n if (!isAbortError(e)) {\n LOG.error(`search operation for query '${query}' failed`, e);\n }\n });\n return operation;\n }\n}\n\n/**\n * Returns the first result across all sources (in source order).\n */\nfunction firstResult(sourceResults: readonly SearchSourceResults[]): SelectResult | undefined {\n for (const { source, results } of sourceResults) {\n const result = results[0];\n if (result) {\n return { source, result };\n }\n }\n return undefined;\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export type { SearchOperationState, SearchSourceResults } from "./SearchOperationState";
2
+ export { SearchViewModel, type SearchViewModelOptions } from "./SearchViewModel";
package/model/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import './SearchViewModel.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}