@open-pioneer/selection 1.4.0 → 1.5.0-dev.20260910103330

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 (60) hide show
  1. package/CHANGELOG.md +47 -29
  2. package/README.md +28 -9
  3. package/_virtual/hooks.js +1 -1
  4. package/_virtual/source-info.js +1 -1
  5. package/api.d.ts +27 -2
  6. package/i18n/de.yaml +0 -6
  7. package/i18n/en.yaml +0 -6
  8. package/index.d.ts +1 -1
  9. package/index.js +1 -1
  10. package/interactions/ExtentSelectionInteraction.d.ts +12 -0
  11. package/interactions/ExtentSelectionInteraction.js +57 -0
  12. package/interactions/ExtentSelectionInteraction.js.map +1 -0
  13. package/model/SelectionViewModel.d.ts +30 -0
  14. package/model/SelectionViewModel.js +270 -0
  15. package/model/SelectionViewModel.js.map +1 -0
  16. package/model/SelectionViewModelFactory.d.ts +21 -0
  17. package/model/SelectionViewModelFactory.js +43 -0
  18. package/model/SelectionViewModelFactory.js.map +1 -0
  19. package/model/index.d.ts +1 -0
  20. package/model/index.js +2 -0
  21. package/model/index.js.map +1 -0
  22. package/package.json +34 -15
  23. package/services.d.ts +2 -7
  24. package/services.js +2 -17
  25. package/services.js.map +1 -1
  26. package/{VectorSelectionSource.d.ts → sources/VectorSelectionSource.d.ts} +3 -7
  27. package/{VectorSelectionSource.js → sources/VectorSelectionSource.js} +12 -9
  28. package/sources/VectorSelectionSource.js.map +1 -0
  29. package/sources/VectorSelectionSourceFactory.d.ts +7 -0
  30. package/sources/VectorSelectionSourceFactory.js +19 -0
  31. package/sources/VectorSelectionSourceFactory.js.map +1 -0
  32. package/{Selection.d.ts → ui/Selection.d.ts} +1 -1
  33. package/ui/Selection.js +97 -0
  34. package/ui/Selection.js.map +1 -0
  35. package/ui/SelectionSourceItem.d.ts +7 -0
  36. package/ui/SelectionSourceItem.js +38 -0
  37. package/ui/SelectionSourceItem.js.map +1 -0
  38. package/ui/useSelectionSourceId.d.ts +9 -0
  39. package/ui/useSelectionSourceId.js +23 -0
  40. package/ui/useSelectionSourceId.js.map +1 -0
  41. package/ui/useSelectionViewModel.d.ts +11 -0
  42. package/ui/useSelectionViewModel.js +43 -0
  43. package/ui/useSelectionViewModel.js.map +1 -0
  44. package/ui/useSourceStatus.d.ts +8 -0
  45. package/ui/useSourceStatus.js +27 -0
  46. package/ui/useSourceStatus.js.map +1 -0
  47. package/DragController.d.ts +0 -33
  48. package/DragController.js +0 -175
  49. package/DragController.js.map +0 -1
  50. package/Selection.js +0 -232
  51. package/Selection.js.map +0 -1
  52. package/SelectionController.d.ts +0 -20
  53. package/SelectionController.js +0 -58
  54. package/SelectionController.js.map +0 -1
  55. package/VectorSelectionSource.js.map +0 -1
  56. /package/{SelectionTooltipContent.d.ts → ui/SelectionTooltipContent.d.ts} +0 -0
  57. /package/{SelectionTooltipContent.js → ui/SelectionTooltipContent.js} +0 -0
  58. /package/{SelectionTooltipContent.js.map → ui/SelectionTooltipContent.js.map} +0 -0
  59. /package/{selection.css → ui/selection.css} +0 -0
  60. /package/{selection.css.map → ui/selection.css.map} +0 -0
@@ -0,0 +1,270 @@
1
+ import { reactive, linked, computed, watchValue, effect } from '@conterra/reactivity-core';
2
+ import { createLogger, shallowEqual, destroyResources, throwAbortError, isAbortError } from '@open-pioneer/core';
3
+ import { sourceId } from '../_virtual/source-info.js';
4
+ import { createElement } from 'react';
5
+ import { ExtentSelectionInteraction } from '../interactions/ExtentSelectionInteraction.js';
6
+ import { SelectionTooltipContent } from '../ui/SelectionTooltipContent.js';
7
+
8
+ const LOG = createLogger(sourceId);
9
+ const DEFAULT_MAX_RESULTS = 1e4;
10
+ const ACTIVE_INTERACTION_CLASS = "selection-active";
11
+ const INACTIVE_INTERACTION_CLASS = "selection-inactive";
12
+ const CLEARED = /* @__PURE__ */ Symbol("cleared");
13
+ class SelectionViewModel {
14
+ #map;
15
+ #messages;
16
+ /** Limits the number of results from a source. */
17
+ #maxResults;
18
+ /** Called whenever results were obtained from a source. */
19
+ #onComplete;
20
+ /** Called whenever an error happens, to show a notification. */
21
+ #onError;
22
+ /** The set of available selection sources. */
23
+ #sources = reactive([], { equal: shallowEqual });
24
+ /**
25
+ * The currently selected selection source (free choice within this.#sources).
26
+ *
27
+ * The state is linked to the set of sources:
28
+ *
29
+ * - while nothing has been selected yet, the first source is selected automatically
30
+ * - if the current source is removed from the set of sources, the selection source is cleared
31
+ *
32
+ * Once the selection source has been cleared that way, it remains empty until a source is
33
+ * selected explicitly again: later updates of the set of sources must not silently
34
+ * re-enable the selection source behind the user's back.
35
+ */
36
+ #currentSource = linked(
37
+ () => this.#sources.value,
38
+ (sources, previousSource) => {
39
+ if (previousSource === CLEARED) {
40
+ return CLEARED;
41
+ }
42
+ if (previousSource) {
43
+ return sources.includes(previousSource) ? previousSource : CLEARED;
44
+ }
45
+ return sources[0];
46
+ }
47
+ );
48
+ #interactionActive = computed(() => {
49
+ const source = this.currentSource;
50
+ return !!source && getSourceStatus(source).kind === "available";
51
+ });
52
+ #ariaMessage = computed(() => {
53
+ const messages = this.#messages;
54
+ if (!this.currentSource) {
55
+ return messages.noSource;
56
+ }
57
+ if (!this.isInteractionActive) {
58
+ return messages.inactive;
59
+ }
60
+ return messages.active;
61
+ });
62
+ // For debugging
63
+ // oxlint-disable-next-line no-unused-private-class-members
64
+ #currentInteraction;
65
+ // For debugging
66
+ // oxlint-disable-next-line no-unused-private-class-members
67
+ #tooltip;
68
+ /** The abort controller of the selection request that is currently running (if any). */
69
+ #currentAbortController;
70
+ /** Resources owned by this instance. */
71
+ #resources;
72
+ constructor(options) {
73
+ this.#map = options.map;
74
+ this.#messages = options.messages;
75
+ this.#maxResults = options.maxResults ?? DEFAULT_MAX_RESULTS;
76
+ this.#onComplete = options.onSelectionComplete;
77
+ this.#onError = options.onError;
78
+ this.#resources = [
79
+ this.#initSelectionInteraction(),
80
+ ...this.#initTooltip(),
81
+ ...this.#initViewport()
82
+ ];
83
+ }
84
+ destroy() {
85
+ this.#abortPendingSelection();
86
+ destroyResources(this.#resources);
87
+ }
88
+ get sources() {
89
+ return this.#sources.value;
90
+ }
91
+ set sources(newSources) {
92
+ this.#sources.value = newSources;
93
+ }
94
+ get currentSource() {
95
+ const source = this.#currentSource.value;
96
+ return source === CLEARED ? void 0 : source;
97
+ }
98
+ set currentSource(source) {
99
+ const sources = this.#sources.value;
100
+ if (sources.length > 0) {
101
+ if (!source) {
102
+ throw new Error(
103
+ "Internal error: cannot select 'undefined' if there are sources present."
104
+ );
105
+ }
106
+ if (!sources.includes(source)) {
107
+ throw new Error("Internal error: cannot select unknown selection source.");
108
+ }
109
+ this.#currentSource.value = source;
110
+ } else {
111
+ if (source) {
112
+ throw new Error(
113
+ "Internal error: can only select 'undefined' if there are no sources present."
114
+ );
115
+ }
116
+ }
117
+ }
118
+ /** Returns true if the selection interaction is currently active. */
119
+ get isInteractionActive() {
120
+ return this.#interactionActive.value;
121
+ }
122
+ /** Aria message to represent the current state. Also serves as the tooltip text. */
123
+ get ariaMessage() {
124
+ return this.#ariaMessage.value;
125
+ }
126
+ /** Runs the selection interaction while active. */
127
+ #initSelectionInteraction() {
128
+ return watchValue(
129
+ () => this.isInteractionActive,
130
+ (isActive) => {
131
+ if (!isActive) {
132
+ return;
133
+ }
134
+ const selection = this.#currentInteraction = new ExtentSelectionInteraction(
135
+ this.#map,
136
+ (geometry) => this.#onGeometrySelected(geometry)
137
+ );
138
+ return () => {
139
+ selection.destroy();
140
+ this.#abortPendingSelection();
141
+ };
142
+ },
143
+ {
144
+ immediate: true
145
+ }
146
+ );
147
+ }
148
+ /**
149
+ * Disables the viewport's context menu
150
+ * and marks the viewport with css classes when selection interaction is active or inactive.
151
+ */
152
+ #initViewport() {
153
+ const viewport = this.#map.olMap.getViewport();
154
+ const disableContextMenu = (e) => {
155
+ e.preventDefault();
156
+ return false;
157
+ };
158
+ viewport.addEventListener("contextmenu", disableContextMenu);
159
+ return [
160
+ {
161
+ destroy() {
162
+ viewport.removeEventListener("contextmenu", disableContextMenu);
163
+ }
164
+ },
165
+ effect(() => {
166
+ const active = this.isInteractionActive;
167
+ const className = active ? ACTIVE_INTERACTION_CLASS : INACTIVE_INTERACTION_CLASS;
168
+ viewport.classList.add(className);
169
+ return () => viewport.classList.remove(className);
170
+ })
171
+ ];
172
+ }
173
+ /**
174
+ * Creates a tooltip that follows the cursor and updates the message depending on the current state.
175
+ */
176
+ #initTooltip() {
177
+ const tooltip = this.#tooltip = createHelpTooltip(this.#map);
178
+ return [
179
+ tooltip,
180
+ watchValue(
181
+ () => this.ariaMessage,
182
+ (message) => {
183
+ const tooltipContent = createElement(SelectionTooltipContent, {
184
+ content: message
185
+ });
186
+ tooltip.setContent(tooltipContent);
187
+ },
188
+ { immediate: true }
189
+ )
190
+ ];
191
+ }
192
+ /**
193
+ * Called after a successful selection interaction on the map.
194
+ * Triggers search on the currently selected selection source.
195
+ */
196
+ async #onGeometrySelected(geometry) {
197
+ const source = this.currentSource;
198
+ if (!source) {
199
+ return;
200
+ }
201
+ this.#abortPendingSelection();
202
+ const abortController = this.#currentAbortController = new AbortController();
203
+ try {
204
+ LOG.debug(`Starting selection on source '${source.label}'`);
205
+ const extent = geometry.getExtent();
206
+ const results = await this.#selectFromSource(source, extent, abortController.signal);
207
+ if (abortController.signal.aborted) {
208
+ throwAbortError();
209
+ }
210
+ LOG.debug(`Found ${results.length} results on source '${source.label}'`);
211
+ this.#onComplete(source, results);
212
+ } catch (e) {
213
+ if (!isAbortError(e)) {
214
+ LOG.error(`selection from source ${source.label} failed`, e);
215
+ if (!abortController.signal.aborted) {
216
+ this.#onError();
217
+ }
218
+ }
219
+ } finally {
220
+ if (this.#currentAbortController === abortController) {
221
+ this.#currentAbortController = void 0;
222
+ }
223
+ }
224
+ }
225
+ async #selectFromSource(source, extent, signal) {
226
+ const map = this.#map;
227
+ const maxResults = this.#maxResults;
228
+ let results = await source.select(
229
+ { type: "extent", extent },
230
+ {
231
+ maxResults,
232
+ map,
233
+ mapProjection: map.projection,
234
+ signal
235
+ }
236
+ );
237
+ if (results.length > maxResults) {
238
+ results = results.slice(0, maxResults);
239
+ }
240
+ return results;
241
+ }
242
+ /** Cancels the current selection request (if any); its results will be ignored. */
243
+ #abortPendingSelection() {
244
+ this.#currentAbortController?.abort();
245
+ this.#currentAbortController = void 0;
246
+ }
247
+ }
248
+ function getSourceStatus(source) {
249
+ const status = source.status;
250
+ if (status == null) {
251
+ return { kind: "available" };
252
+ }
253
+ if (typeof status == "string") {
254
+ return { kind: status };
255
+ }
256
+ return status;
257
+ }
258
+ function createHelpTooltip(map) {
259
+ const overlay = map.overlays.add({
260
+ position: "follow-pointer",
261
+ offset: [15, 0],
262
+ positioning: "center-left",
263
+ ariaRole: "tooltip",
264
+ className: "selection-tooltip printing-hide"
265
+ });
266
+ return overlay;
267
+ }
268
+
269
+ export { SelectionViewModel, getSourceStatus };
270
+ //# sourceMappingURL=SelectionViewModel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SelectionViewModel.js","sources":["SelectionViewModel.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2023-2025 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\n\nimport { computed, effect, linked, reactive, watchValue } from \"@conterra/reactivity-core\";\nimport {\n createLogger,\n destroyResources,\n isAbortError,\n Resource,\n shallowEqual,\n throwAbortError\n} from \"@open-pioneer/core\";\nimport { MapModel, Overlay } from \"@open-pioneer/map\";\nimport { Extent } from \"ol/extent\";\nimport { Geometry } from \"ol/geom\";\nimport { sourceId } from \"open-pioneer:source-info\";\nimport { createElement } from \"react\";\nimport { SelectionResult, SelectionSource, SelectionSourceStatusObject } from \"../api\";\nimport { ExtentSelectionInteraction } from \"../interactions/ExtentSelectionInteraction\";\nimport { SelectionTooltipContent } from \"../ui/SelectionTooltipContent\";\n\nconst LOG = createLogger(sourceId);\n\nconst DEFAULT_MAX_RESULTS = 10000;\n\nconst ACTIVE_INTERACTION_CLASS = \"selection-active\";\nconst INACTIVE_INTERACTION_CLASS = \"selection-inactive\";\n\nexport interface Messages {\n active: string;\n inactive: string;\n noSource: string;\n}\n\n/**\n * Marker for a selection source that has been cleared.\n * The cleared state (when the selected source has been \"lost\") should be sticky, a new source\n * should not be selected automatically.\n */\nconst CLEARED = Symbol(\"cleared\");\n\nexport class SelectionViewModel {\n #map: MapModel;\n #messages: Messages;\n\n /** Limits the number of results from a source. */\n #maxResults: number;\n\n /** Called whenever results were obtained from a source. */\n #onComplete: (source: SelectionSource, results: SelectionResult[]) => void;\n\n /** Called whenever an error happens, to show a notification. */\n #onError: () => void;\n\n /** The set of available selection sources. */\n #sources = reactive<SelectionSource[]>([], { equal: shallowEqual });\n\n /**\n * The currently selected selection source (free choice within this.#sources).\n *\n * The state is linked to the set of sources:\n *\n * - while nothing has been selected yet, the first source is selected automatically\n * - if the current source is removed from the set of sources, the selection source is cleared\n *\n * Once the selection source has been cleared that way, it remains empty until a source is\n * selected explicitly again: later updates of the set of sources must not silently\n * re-enable the selection source behind the user's back.\n */\n #currentSource = linked(\n () => this.#sources.value,\n (\n sources: SelectionSource[],\n previousSource: SelectionSource | typeof CLEARED | undefined\n ): SelectionSource | typeof CLEARED | undefined => {\n if (previousSource === CLEARED) {\n return CLEARED;\n }\n if (previousSource) {\n return sources.includes(previousSource) ? previousSource : CLEARED;\n }\n return sources[0];\n }\n );\n\n #interactionActive = computed(() => {\n const source = this.currentSource;\n return !!source && getSourceStatus(source).kind === \"available\";\n });\n\n #ariaMessage = computed(() => {\n const messages = this.#messages;\n if (!this.currentSource) {\n return messages.noSource;\n }\n if (!this.isInteractionActive) {\n return messages.inactive;\n }\n return messages.active;\n });\n\n // For debugging\n // oxlint-disable-next-line no-unused-private-class-members\n #currentInteraction: ExtentSelectionInteraction | undefined;\n\n // For debugging\n // oxlint-disable-next-line no-unused-private-class-members\n #tooltip: Overlay | undefined;\n\n /** The abort controller of the selection request that is currently running (if any). */\n #currentAbortController: AbortController | undefined;\n\n /** Resources owned by this instance. */\n #resources: Resource[];\n\n constructor(options: {\n map: MapModel;\n messages: Messages;\n onSelectionComplete: (source: SelectionSource, results: SelectionResult[]) => void;\n onError: () => void;\n maxResults?: number;\n }) {\n this.#map = options.map;\n this.#messages = options.messages;\n this.#maxResults = options.maxResults ?? DEFAULT_MAX_RESULTS;\n this.#onComplete = options.onSelectionComplete;\n this.#onError = options.onError;\n this.#resources = [\n this.#initSelectionInteraction(),\n ...this.#initTooltip(),\n ...this.#initViewport()\n ];\n }\n\n destroy() {\n this.#abortPendingSelection();\n destroyResources(this.#resources);\n }\n\n get sources(): SelectionSource[] {\n return this.#sources.value;\n }\n\n set sources(newSources: SelectionSource[]) {\n this.#sources.value = newSources;\n }\n\n get currentSource(): SelectionSource | undefined {\n const source = this.#currentSource.value;\n return source === CLEARED ? undefined : source;\n }\n\n set currentSource(source: SelectionSource | undefined) {\n const sources = this.#sources.value;\n if (sources.length > 0) {\n if (!source) {\n throw new Error(\n \"Internal error: cannot select 'undefined' if there are sources present.\"\n );\n }\n if (!sources.includes(source)) {\n throw new Error(\"Internal error: cannot select unknown selection source.\");\n }\n this.#currentSource.value = source;\n } else {\n if (source) {\n throw new Error(\n \"Internal error: can only select 'undefined' if there are no sources present.\"\n );\n }\n // No source can be selected; the current state is already empty.\n }\n }\n\n /** Returns true if the selection interaction is currently active. */\n get isInteractionActive(): boolean {\n return this.#interactionActive.value;\n }\n\n /** Aria message to represent the current state. Also serves as the tooltip text. */\n get ariaMessage(): string {\n return this.#ariaMessage.value;\n }\n\n /** Runs the selection interaction while active. */\n #initSelectionInteraction(): Resource {\n return watchValue(\n () => this.isInteractionActive,\n (isActive) => {\n if (!isActive) {\n return;\n }\n\n const selection = (this.#currentInteraction = new ExtentSelectionInteraction(\n this.#map,\n (geometry) => this.#onGeometrySelected(geometry)\n ));\n return () => {\n selection.destroy();\n this.#abortPendingSelection();\n };\n },\n {\n immediate: true\n }\n );\n }\n\n /**\n * Disables the viewport's context menu\n * and marks the viewport with css classes when selection interaction is active or inactive.\n */\n #initViewport(): Resource[] {\n const viewport = this.#map.olMap.getViewport();\n const disableContextMenu = (e: MouseEvent) => {\n e.preventDefault();\n return false;\n };\n viewport.addEventListener(\"contextmenu\", disableContextMenu);\n\n return [\n {\n destroy() {\n viewport.removeEventListener(\"contextmenu\", disableContextMenu);\n }\n },\n effect(() => {\n const active = this.isInteractionActive;\n const className = active ? ACTIVE_INTERACTION_CLASS : INACTIVE_INTERACTION_CLASS;\n viewport.classList.add(className);\n return () => viewport.classList.remove(className);\n })\n ];\n }\n\n /**\n * Creates a tooltip that follows the cursor and updates the message depending on the current state.\n */\n #initTooltip(): Resource[] {\n const tooltip = (this.#tooltip = createHelpTooltip(this.#map));\n return [\n tooltip,\n watchValue(\n () => this.ariaMessage,\n (message) => {\n // Aria message doubles as tooltip text at this time\n const tooltipContent = createElement(SelectionTooltipContent, {\n content: message\n });\n tooltip.setContent(tooltipContent);\n },\n { immediate: true }\n )\n ];\n }\n\n /**\n * Called after a successful selection interaction on the map.\n * Triggers search on the currently selected selection source.\n */\n async #onGeometrySelected(geometry: Geometry) {\n const source = this.currentSource;\n if (!source) {\n return;\n }\n\n // Only the most recent selection is of interest; cancel the previous one (if any).\n this.#abortPendingSelection();\n\n const abortController = (this.#currentAbortController = new AbortController());\n try {\n LOG.debug(`Starting selection on source '${source.label}'`);\n\n const extent = geometry.getExtent();\n const results = await this.#selectFromSource(source, extent, abortController.signal);\n if (abortController.signal.aborted) {\n // Superseded by another selection, or the widget is gone.\n throwAbortError();\n }\n\n LOG.debug(`Found ${results.length} results on source '${source.label}'`);\n this.#onComplete(source, results);\n } catch (e) {\n if (!isAbortError(e)) {\n LOG.error(`selection from source ${source.label} failed`, e);\n if (!abortController.signal.aborted) {\n this.#onError();\n }\n }\n } finally {\n if (this.#currentAbortController === abortController) {\n this.#currentAbortController = undefined;\n }\n }\n }\n\n async #selectFromSource(source: SelectionSource, extent: Extent, signal: AbortSignal) {\n const map = this.#map;\n const maxResults = this.#maxResults;\n let results = await source.select(\n { type: \"extent\", extent },\n {\n maxResults,\n map: map,\n mapProjection: map.projection,\n signal\n }\n );\n if (results.length > maxResults) {\n results = results.slice(0, maxResults);\n }\n return results;\n }\n\n /** Cancels the current selection request (if any); its results will be ignored. */\n #abortPendingSelection(): void {\n this.#currentAbortController?.abort();\n this.#currentAbortController = undefined;\n }\n}\n\n/**\n * Normalizes the source's status into an object.\n */\nexport function getSourceStatus(source: SelectionSource): SelectionSourceStatusObject {\n const status = source.status;\n if (status == null) {\n return { kind: \"available\" };\n }\n if (typeof status == \"string\") {\n return { kind: status };\n }\n return status;\n}\n\nfunction createHelpTooltip(map: MapModel): Overlay {\n const overlay = map.overlays.add({\n position: \"follow-pointer\",\n offset: [15, 0],\n positioning: \"center-left\",\n ariaRole: \"tooltip\",\n className: \"selection-tooltip printing-hide\"\n });\n return overlay;\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import { MapModel } from "@open-pioneer/map";
2
+ import { NotificationService } from "@open-pioneer/notifier";
3
+ import { DECLARE_SERVICE_INTERFACE, ServiceOptions } from "@open-pioneer/runtime";
4
+ import { SelectionResult, SelectionSource } from "../api";
5
+ import { SelectionViewModel } from "./SelectionViewModel";
6
+ interface References {
7
+ notifier: NotificationService;
8
+ }
9
+ /**
10
+ * @internal
11
+ */
12
+ export declare class SelectionViewModelFactory {
13
+ #private;
14
+ [DECLARE_SERVICE_INTERFACE]: "selection.ViewModelFactory";
15
+ constructor({ references, currentIntl }: ServiceOptions<References>);
16
+ createViewModel(options: {
17
+ map: MapModel;
18
+ onSelectionComplete: (source: SelectionSource, results: SelectionResult[]) => void;
19
+ }): SelectionViewModel;
20
+ }
21
+ export {};
@@ -0,0 +1,43 @@
1
+ import { FormattedMessage } from '@open-pioneer/react-utils';
2
+ import { createElement } from 'react';
3
+ import { SelectionViewModel } from './SelectionViewModel.js';
4
+
5
+ class SelectionViewModelFactory {
6
+ #notifier;
7
+ #currentIntl;
8
+ #messages;
9
+ constructor({ references, currentIntl }) {
10
+ this.#notifier = references.notifier;
11
+ this.#currentIntl = currentIntl;
12
+ this.#messages = {
13
+ get active() {
14
+ return currentIntl.value.formatMessage({ id: "tooltip" });
15
+ },
16
+ get inactive() {
17
+ return currentIntl.value.formatMessage({ id: "disabledTooltip" });
18
+ },
19
+ get noSource() {
20
+ return currentIntl.value.formatMessage({ id: "noSourceTooltip" });
21
+ }
22
+ };
23
+ }
24
+ createViewModel(options) {
25
+ return new SelectionViewModel({
26
+ map: options.map,
27
+ messages: this.#messages,
28
+ onSelectionComplete: options.onSelectionComplete,
29
+ onError: () => {
30
+ this.#notifier.notify({
31
+ level: "error",
32
+ message: createElement(FormattedMessage, {
33
+ intl: this.#currentIntl,
34
+ id: "selectionFailed"
35
+ })
36
+ });
37
+ }
38
+ });
39
+ }
40
+ }
41
+
42
+ export { SelectionViewModelFactory };
43
+ //# sourceMappingURL=SelectionViewModelFactory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SelectionViewModelFactory.js","sources":["SelectionViewModelFactory.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2023-2025 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\n\nimport { ReadonlyReactive } from \"@conterra/reactivity-core\";\nimport { MapModel } from \"@open-pioneer/map\";\nimport { NotificationService } from \"@open-pioneer/notifier\";\nimport { FormattedMessage } from \"@open-pioneer/react-utils\";\nimport { DECLARE_SERVICE_INTERFACE, PackageIntl, ServiceOptions } from \"@open-pioneer/runtime\";\nimport { createElement } from \"react\";\nimport { SelectionResult, SelectionSource } from \"../api\";\nimport { Messages, SelectionViewModel } from \"./SelectionViewModel\";\n\ninterface References {\n notifier: NotificationService;\n}\n\n/**\n * @internal\n */\nexport class SelectionViewModelFactory {\n declare [DECLARE_SERVICE_INTERFACE]: \"selection.ViewModelFactory\";\n\n #notifier: NotificationService;\n #currentIntl: ReadonlyReactive<PackageIntl>;\n #messages: Messages;\n\n constructor({ references, currentIntl }: ServiceOptions<References>) {\n this.#notifier = references.notifier;\n this.#currentIntl = currentIntl;\n this.#messages = {\n get active() {\n return currentIntl.value.formatMessage({ id: \"tooltip\" });\n },\n get inactive() {\n return currentIntl.value.formatMessage({ id: \"disabledTooltip\" });\n },\n get noSource() {\n return currentIntl.value.formatMessage({ id: \"noSourceTooltip\" });\n }\n };\n }\n\n createViewModel(options: {\n map: MapModel;\n onSelectionComplete: (source: SelectionSource, results: SelectionResult[]) => void;\n }): SelectionViewModel {\n return new SelectionViewModel({\n map: options.map,\n messages: this.#messages,\n onSelectionComplete: options.onSelectionComplete,\n onError: () => {\n this.#notifier.notify({\n level: \"error\",\n message: createElement(FormattedMessage, {\n intl: this.#currentIntl,\n id: \"selectionFailed\"\n })\n });\n }\n });\n }\n}\n"],"names":[],"mappings":";;;;AAmBO,MAAM,yBAAA,CAA0B;AAAA,EAGnC,SAAA;AAAA,EACA,YAAA;AAAA,EACA,SAAA;AAAA,EAEA,WAAA,CAAY,EAAE,UAAA,EAAY,WAAA,EAAY,EAA+B;AACjE,IAAA,IAAA,CAAK,YAAY,UAAA,CAAW,QAAA;AAC5B,IAAA,IAAA,CAAK,YAAA,GAAe,WAAA;AACpB,IAAA,IAAA,CAAK,SAAA,GAAY;AAAA,MACb,IAAI,MAAA,GAAS;AACT,QAAA,OAAO,YAAY,KAAA,CAAM,aAAA,CAAc,EAAE,EAAA,EAAI,WAAW,CAAA;AAAA,MAC5D,CAAA;AAAA,MACA,IAAI,QAAA,GAAW;AACX,QAAA,OAAO,YAAY,KAAA,CAAM,aAAA,CAAc,EAAE,EAAA,EAAI,mBAAmB,CAAA;AAAA,MACpE,CAAA;AAAA,MACA,IAAI,QAAA,GAAW;AACX,QAAA,OAAO,YAAY,KAAA,CAAM,aAAA,CAAc,EAAE,EAAA,EAAI,mBAAmB,CAAA;AAAA,MACpE;AAAA,KACJ;AAAA,EACJ;AAAA,EAEA,gBAAgB,OAAA,EAGO;AACnB,IAAA,OAAO,IAAI,kBAAA,CAAmB;AAAA,MAC1B,KAAK,OAAA,CAAQ,GAAA;AAAA,MACb,UAAU,IAAA,CAAK,SAAA;AAAA,MACf,qBAAqB,OAAA,CAAQ,mBAAA;AAAA,MAC7B,SAAS,MAAM;AACX,QAAA,IAAA,CAAK,UAAU,MAAA,CAAO;AAAA,UAClB,KAAA,EAAO,OAAA;AAAA,UACP,OAAA,EAAS,cAAc,gBAAA,EAAkB;AAAA,YACrC,MAAM,IAAA,CAAK,YAAA;AAAA,YACX,EAAA,EAAI;AAAA,WACP;AAAA,SACJ,CAAA;AAAA,MACL;AAAA,KACH,CAAA;AAAA,EACL;AACJ;;;;"}
@@ -0,0 +1 @@
1
+ export { SelectionViewModel, getSourceStatus, type Messages } from "./SelectionViewModel";
package/model/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import './SelectionViewModel.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@open-pioneer/selection",
4
- "version": "1.4.0",
4
+ "version": "1.5.0-dev.20260910103330",
5
5
  "description": "This package provides a UI component to perform a selection on given selection sources from the map.",
6
6
  "keywords": [
7
7
  "open-pioneer-trails"
@@ -14,19 +14,23 @@
14
14
  "directory": "src/packages/selection"
15
15
  },
16
16
  "dependencies": {
17
- "@chakra-ui/react": "^3.36.1",
18
- "@conterra/reactivity-core": "^0.8.6",
19
- "@open-pioneer/chakra-snippets": "^4.7.0",
20
- "@open-pioneer/core": "^4.7.0",
21
- "@open-pioneer/notifier": "^4.7.0",
22
- "@open-pioneer/react-utils": "^4.7.0",
23
- "@open-pioneer/reactivity": "^4.7.0",
24
- "@open-pioneer/runtime": "^4.7.0",
25
- "ol": "^10.9.0",
17
+ "@chakra-ui/react": "^3.37.0",
18
+ "@conterra/reactivity-core": "^0.8.8",
19
+ "@open-pioneer/chakra-snippets": "4.8.0-dev.20260910101405",
20
+ "@open-pioneer/core": "4.8.0-dev.20260910101405",
21
+ "@open-pioneer/map": "1.5.0-dev.20260910103330",
22
+ "@open-pioneer/notifier": "4.8.0-dev.20260910101405",
23
+ "@open-pioneer/react-utils": "4.8.0-dev.20260910101405",
24
+ "@open-pioneer/reactivity": "4.8.0-dev.20260910101405",
25
+ "@open-pioneer/runtime": "4.8.0-dev.20260910101405",
26
+ "ol": "^10.10.0",
26
27
  "react": "^19.2.8",
27
28
  "react-icons": "^5.7.0",
28
- "uuid": "^14.0.1",
29
- "@open-pioneer/map": "1.4.0"
29
+ "uuid": "^14.0.2"
30
+ },
31
+ "publishConfig": {
32
+ "directory": "dist",
33
+ "linkDirectory": false
30
34
  },
31
35
  "exports": {
32
36
  "./package.json": "./package.json",
@@ -38,11 +42,26 @@
38
42
  "import": "./services.js",
39
43
  "types": "./services.d.ts"
40
44
  },
41
- "./selection.css": "./selection.css"
45
+ "./ui/selection.css": "./ui/selection.css"
42
46
  },
43
47
  "openPioneerFramework": {
44
- "styles": "./selection.css",
48
+ "styles": "./ui/selection.css",
45
49
  "services": [
50
+ {
51
+ "serviceName": "SelectionViewModelFactory",
52
+ "provides": [
53
+ {
54
+ "interfaceName": "selection.ViewModelFactory"
55
+ }
56
+ ],
57
+ "references": [
58
+ {
59
+ "referenceName": "notifier",
60
+ "type": "unique",
61
+ "interfaceName": "notifier.NotificationService"
62
+ }
63
+ ]
64
+ },
46
65
  {
47
66
  "serviceName": "VectorSelectionSourceFactory",
48
67
  "provides": [
@@ -64,7 +83,7 @@
64
83
  "references": [
65
84
  {
66
85
  "type": "unique",
67
- "interfaceName": "notifier.NotificationService"
86
+ "interfaceName": "selection.ViewModelFactory"
68
87
  }
69
88
  ]
70
89
  },
package/services.d.ts CHANGED
@@ -1,7 +1,2 @@
1
- import { type PackageIntl, ServiceOptions } from "@open-pioneer/runtime";
2
- import { VectorLayerSelectionSource, VectorLayerSelectionSourceFactory, VectorLayerSelectionSourceOptions } from "./api";
3
- export declare class VectorSelectionSourceFactory implements VectorLayerSelectionSourceFactory {
4
- #private;
5
- constructor({ currentIntl }: ServiceOptions<PackageIntl>);
6
- createSelectionSource(options: VectorLayerSelectionSourceOptions): VectorLayerSelectionSource;
7
- }
1
+ export { VectorSelectionSourceFactory } from "./sources/VectorSelectionSourceFactory";
2
+ export { SelectionViewModelFactory } from "./model/SelectionViewModelFactory";
package/services.js CHANGED
@@ -1,18 +1,3 @@
1
- import { VectorLayerSelectionSourceImpl } from './VectorSelectionSource.js';
2
-
3
- class VectorSelectionSourceFactory {
4
- #currentIntl;
5
- constructor({ currentIntl }) {
6
- this.#currentIntl = currentIntl;
7
- }
8
- createSelectionSource(options) {
9
- return new VectorLayerSelectionSourceImpl(
10
- options.vectorLayer,
11
- options.label,
12
- this.#currentIntl
13
- );
14
- }
15
- }
16
-
17
- export { VectorSelectionSourceFactory };
1
+ export { VectorSelectionSourceFactory } from './sources/VectorSelectionSourceFactory.js';
2
+ export { SelectionViewModelFactory } from './model/SelectionViewModelFactory.js';
18
3
  //# sourceMappingURL=services.js.map
package/services.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"services.js","sources":["services.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2023-2025 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\n\nimport { ReadonlyReactive } from \"@conterra/reactivity-core\";\nimport { type PackageIntl, ServiceOptions } from \"@open-pioneer/runtime\";\nimport {\n VectorLayerSelectionSource,\n VectorLayerSelectionSourceFactory,\n VectorLayerSelectionSourceOptions\n} from \"./api\";\nimport { VectorLayerSelectionSourceImpl } from \"./VectorSelectionSource\";\n\nexport class VectorSelectionSourceFactory implements VectorLayerSelectionSourceFactory {\n #currentIntl: ReadonlyReactive<PackageIntl>;\n\n constructor({ currentIntl }: ServiceOptions<PackageIntl>) {\n this.#currentIntl = currentIntl;\n }\n createSelectionSource(options: VectorLayerSelectionSourceOptions): VectorLayerSelectionSource {\n return new VectorLayerSelectionSourceImpl(\n options.vectorLayer,\n options.label,\n this.#currentIntl\n );\n }\n}\n"],"names":[],"mappings":";;AAYO,MAAM,4BAAA,CAA0E;AAAA,EACnF,YAAA;AAAA,EAEA,WAAA,CAAY,EAAE,WAAA,EAAY,EAAgC;AACtD,IAAA,IAAA,CAAK,YAAA,GAAe,WAAA;AAAA,EACxB;AAAA,EACA,sBAAsB,OAAA,EAAwE;AAC1F,IAAA,OAAO,IAAI,8BAAA;AAAA,MACP,OAAA,CAAQ,WAAA;AAAA,MACR,OAAA,CAAQ,KAAA;AAAA,MACR,IAAA,CAAK;AAAA,KACT;AAAA,EACJ;AACJ;;;;"}
1
+ {"version":3,"file":"services.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -3,19 +3,15 @@ import { PackageIntl } from "@open-pioneer/runtime";
3
3
  import Feature from "ol/Feature";
4
4
  import VectorLayer from "ol/layer/Vector";
5
5
  import VectorSource from "ol/source/Vector";
6
- import { SelectionResult, SelectionOptions, SelectionKind, VectorLayerSelectionSource, SelectionSourceStatusObject } from "./api";
6
+ import { SelectionKind, SelectionOptions, SelectionResult, SelectionSourceStatusObject, VectorLayerSelectionSource } from "../api";
7
7
  /**
8
8
  * A SelectionSource to use an OpenLayers VectorLayer with an OpenLayers VectorSource (e.g. layer of the map).
9
- * Features are:
10
- * - using only the extent as selection kind
11
- * - listening to layer visibility changes and updating the status of the source
12
- * - limiting the number of returned selection results to the corresponding selection option
13
- * - throwing an event `changed:status` when the status updates
14
9
  */
15
10
  export declare class VectorLayerSelectionSourceImpl implements VectorLayerSelectionSource {
16
11
  #private;
12
+ readonly id: string | undefined;
17
13
  readonly label: string;
18
- constructor(vectorLayer: VectorLayer<VectorSource, Feature>, label: string, currentIntl: ReadonlyReactive<PackageIntl>);
14
+ constructor(id: string | undefined, vectorLayer: VectorLayer<VectorSource, Feature>, label: string, currentIntl: ReadonlyReactive<PackageIntl>);
19
15
  destroy(): void;
20
16
  get status(): SelectionSourceStatusObject;
21
17
  select(selectionKind: SelectionKind, options: SelectionOptions): Promise<SelectionResult[]>;
@@ -1,31 +1,34 @@
1
- import { reactive, computed } from '@conterra/reactivity-core';
1
+ import { synchronized, computed } from '@conterra/reactivity-core';
2
2
  import { unByKey } from 'ol/Observable.js';
3
3
  import { v4 } from 'uuid';
4
4
 
5
5
  class VectorLayerSelectionSourceImpl {
6
+ id;
6
7
  label;
7
8
  #vectorLayer;
8
- #eventHandler;
9
9
  #currentIntl;
10
- #layerVisible = reactive(true);
10
+ #layerVisible;
11
11
  #status;
12
- constructor(vectorLayer, label, currentIntl) {
12
+ constructor(id, vectorLayer, label, currentIntl) {
13
+ this.id = id;
13
14
  this.label = label;
14
15
  this.#vectorLayer = vectorLayer;
15
16
  this.#currentIntl = currentIntl;
16
- this.#layerVisible.value = vectorLayer.getVisible();
17
+ this.#layerVisible = synchronized(
18
+ () => vectorLayer.getVisible(),
19
+ (cb) => {
20
+ const key = vectorLayer.on("change:visible", cb);
21
+ return () => unByKey(key);
22
+ }
23
+ );
17
24
  this.#status = computed(
18
25
  () => this.#layerVisible.value ? { kind: "available" } : {
19
26
  kind: "unavailable",
20
27
  reason: this.#currentIntl.value.formatMessage({ id: "layerNotVisibleReason" })
21
28
  }
22
29
  );
23
- this.#eventHandler = this.#vectorLayer.on("change:visible", () => {
24
- this.#layerVisible.value = this.#vectorLayer.getVisible();
25
- });
26
30
  }
27
31
  destroy() {
28
- unByKey(this.#eventHandler);
29
32
  }
30
33
  get status() {
31
34
  return this.#status.value;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VectorSelectionSource.js","sources":["VectorSelectionSource.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2023-2025 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\n\nimport { computed, ReadonlyReactive, synchronized } from \"@conterra/reactivity-core\";\nimport { PackageIntl } from \"@open-pioneer/runtime\";\nimport Feature from \"ol/Feature\";\nimport VectorLayer from \"ol/layer/Vector\";\nimport { unByKey } from \"ol/Observable\";\nimport VectorSource from \"ol/source/Vector\";\nimport { v4 as uuid4v } from \"uuid\";\nimport {\n SelectionKind,\n SelectionOptions,\n SelectionResult,\n SelectionSourceStatusObject,\n VectorLayerSelectionSource\n} from \"../api\";\n\n/**\n * A SelectionSource to use an OpenLayers VectorLayer with an OpenLayers VectorSource (e.g. layer of the map).\n */\nexport class VectorLayerSelectionSourceImpl implements VectorLayerSelectionSource {\n readonly id: string | undefined;\n readonly label: string;\n #vectorLayer: VectorLayer<VectorSource, Feature>;\n #currentIntl: ReadonlyReactive<PackageIntl>;\n #layerVisible: ReadonlyReactive<boolean>;\n #status: ReadonlyReactive<SelectionSourceStatusObject>;\n\n constructor(\n id: string | undefined,\n vectorLayer: VectorLayer<VectorSource, Feature>,\n label: string,\n currentIntl: ReadonlyReactive<PackageIntl>\n ) {\n this.id = id;\n this.label = label;\n this.#vectorLayer = vectorLayer;\n this.#currentIntl = currentIntl;\n this.#layerVisible = synchronized(\n () => vectorLayer.getVisible(),\n (cb) => {\n const key = vectorLayer.on(\"change:visible\", cb);\n return () => unByKey(key);\n }\n );\n this.#status = computed<SelectionSourceStatusObject>(() =>\n this.#layerVisible.value\n ? { kind: \"available\" }\n : {\n kind: \"unavailable\",\n reason: this.#currentIntl.value.formatMessage({ id: \"layerNotVisibleReason\" })\n }\n );\n }\n\n destroy() {}\n\n get status() {\n return this.#status.value;\n }\n\n async select(\n selectionKind: SelectionKind,\n options: SelectionOptions\n ): Promise<SelectionResult[]> {\n if (selectionKind.type !== \"extent\") {\n throw new Error(`Unsupported selection kind: ${selectionKind.type}`);\n }\n\n if (this.#status.value.kind !== \"available\" || this.#vectorLayer.getSource() === null)\n return [];\n\n const allResults: SelectionResult[] = [];\n // oxlint-disable-next-line @typescript-eslint/no-non-null-assertion\n this.#vectorLayer\n .getSource()!\n .forEachFeatureIntersectingExtent(selectionKind.extent, (feature) => {\n if (!feature.getGeometry()) return;\n\n // TODO: Think about where to implement Date-Formatting, if the dates are already\n // encoded as Strings...\n\n const filteredProperties = { ...feature.getProperties() };\n delete filteredProperties.geometries;\n\n const result: SelectionResult = {\n id: feature.getId()?.toString() || uuid4v(),\n // oxlint-disable-next-line @typescript-eslint/no-non-null-assertion\n geometry: feature.getGeometry()!,\n properties: filteredProperties\n };\n\n allResults.push(result);\n });\n const selectedFeatures = allResults.filter((s): s is SelectionResult => s != null);\n const limitedFeatures =\n selectedFeatures.length > options.maxResults\n ? selectedFeatures.slice(0, options.maxResults)\n : selectedFeatures;\n return limitedFeatures;\n }\n}\n"],"names":["uuid4v"],"mappings":";;;;AAqBO,MAAM,8BAAA,CAAqE;AAAA,EACrE,EAAA;AAAA,EACA,KAAA;AAAA,EACT,YAAA;AAAA,EACA,YAAA;AAAA,EACA,aAAA;AAAA,EACA,OAAA;AAAA,EAEA,WAAA,CACI,EAAA,EACA,WAAA,EACA,KAAA,EACA,WAAA,EACF;AACE,IAAA,IAAA,CAAK,EAAA,GAAK,EAAA;AACV,IAAA,IAAA,CAAK,KAAA,GAAQ,KAAA;AACb,IAAA,IAAA,CAAK,YAAA,GAAe,WAAA;AACpB,IAAA,IAAA,CAAK,YAAA,GAAe,WAAA;AACpB,IAAA,IAAA,CAAK,aAAA,GAAgB,YAAA;AAAA,MACjB,MAAM,YAAY,UAAA,EAAW;AAAA,MAC7B,CAAC,EAAA,KAAO;AACJ,QAAA,MAAM,GAAA,GAAM,WAAA,CAAY,EAAA,CAAG,gBAAA,EAAkB,EAAE,CAAA;AAC/C,QAAA,OAAO,MAAM,QAAQ,GAAG,CAAA;AAAA,MAC5B;AAAA,KACJ;AACA,IAAA,IAAA,CAAK,OAAA,GAAU,QAAA;AAAA,MAAsC,MACjD,IAAA,CAAK,aAAA,CAAc,QACb,EAAE,IAAA,EAAM,aAAY,GACpB;AAAA,QACI,IAAA,EAAM,aAAA;AAAA,QACN,MAAA,EAAQ,KAAK,YAAA,CAAa,KAAA,CAAM,cAAc,EAAE,EAAA,EAAI,yBAAyB;AAAA;AACjF,KACV;AAAA,EACJ;AAAA,EAEA,OAAA,GAAU;AAAA,EAAC;AAAA,EAEX,IAAI,MAAA,GAAS;AACT,IAAA,OAAO,KAAK,OAAA,CAAQ,KAAA;AAAA,EACxB;AAAA,EAEA,MAAM,MAAA,CACF,aAAA,EACA,OAAA,EAC0B;AAC1B,IAAA,IAAI,aAAA,CAAc,SAAS,QAAA,EAAU;AACjC,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,4BAAA,EAA+B,aAAA,CAAc,IAAI,CAAA,CAAE,CAAA;AAAA,IACvE;AAEA,IAAA,IAAI,IAAA,CAAK,QAAQ,KAAA,CAAM,IAAA,KAAS,eAAe,IAAA,CAAK,YAAA,CAAa,WAAU,KAAM,IAAA;AAC7E,MAAA,OAAO,EAAC;AAEZ,IAAA,MAAM,aAAgC,EAAC;AAEvC,IAAA,IAAA,CAAK,aACA,SAAA,EAAU,CACV,iCAAiC,aAAA,CAAc,MAAA,EAAQ,CAAC,OAAA,KAAY;AACjE,MAAA,IAAI,CAAC,OAAA,CAAQ,WAAA,EAAY,EAAG;AAK5B,MAAA,MAAM,kBAAA,GAAqB,EAAE,GAAG,OAAA,CAAQ,eAAc,EAAE;AACxD,MAAA,OAAO,kBAAA,CAAmB,UAAA;AAE1B,MAAA,MAAM,MAAA,GAA0B;AAAA,QAC5B,IAAI,OAAA,CAAQ,KAAA,EAAM,EAAG,QAAA,MAAcA,EAAA,EAAO;AAAA;AAAA,QAE1C,QAAA,EAAU,QAAQ,WAAA,EAAY;AAAA,QAC9B,UAAA,EAAY;AAAA,OAChB;AAEA,MAAA,UAAA,CAAW,KAAK,MAAM,CAAA;AAAA,IAC1B,CAAC,CAAA;AACL,IAAA,MAAM,mBAAmB,UAAA,CAAW,MAAA,CAAO,CAAC,CAAA,KAA4B,KAAK,IAAI,CAAA;AACjF,IAAA,MAAM,eAAA,GACF,gBAAA,CAAiB,MAAA,GAAS,OAAA,CAAQ,UAAA,GAC5B,iBAAiB,KAAA,CAAM,CAAA,EAAG,OAAA,CAAQ,UAAU,CAAA,GAC5C,gBAAA;AACV,IAAA,OAAO,eAAA;AAAA,EACX;AACJ;;;;"}
@@ -0,0 +1,7 @@
1
+ import { type PackageIntl, ServiceOptions } from "@open-pioneer/runtime";
2
+ import { VectorLayerSelectionSource, VectorLayerSelectionSourceFactory, VectorLayerSelectionSourceOptions } from "../api";
3
+ export declare class VectorSelectionSourceFactory implements VectorLayerSelectionSourceFactory {
4
+ #private;
5
+ constructor({ currentIntl }: ServiceOptions<PackageIntl>);
6
+ createSelectionSource(options: VectorLayerSelectionSourceOptions): VectorLayerSelectionSource;
7
+ }
@@ -0,0 +1,19 @@
1
+ import { VectorLayerSelectionSourceImpl } from './VectorSelectionSource.js';
2
+
3
+ class VectorSelectionSourceFactory {
4
+ #currentIntl;
5
+ constructor({ currentIntl }) {
6
+ this.#currentIntl = currentIntl;
7
+ }
8
+ createSelectionSource(options) {
9
+ return new VectorLayerSelectionSourceImpl(
10
+ options.id,
11
+ options.vectorLayer,
12
+ options.label,
13
+ this.#currentIntl
14
+ );
15
+ }
16
+ }
17
+
18
+ export { VectorSelectionSourceFactory };
19
+ //# sourceMappingURL=VectorSelectionSourceFactory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VectorSelectionSourceFactory.js","sources":["VectorSelectionSourceFactory.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2023-2025 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\n\nimport { ReadonlyReactive } from \"@conterra/reactivity-core\";\nimport { type PackageIntl, ServiceOptions } from \"@open-pioneer/runtime\";\nimport {\n VectorLayerSelectionSource,\n VectorLayerSelectionSourceFactory,\n VectorLayerSelectionSourceOptions\n} from \"../api\";\nimport { VectorLayerSelectionSourceImpl } from \"./VectorSelectionSource\";\n\nexport class VectorSelectionSourceFactory implements VectorLayerSelectionSourceFactory {\n #currentIntl: ReadonlyReactive<PackageIntl>;\n\n constructor({ currentIntl }: ServiceOptions<PackageIntl>) {\n this.#currentIntl = currentIntl;\n }\n\n createSelectionSource(options: VectorLayerSelectionSourceOptions): VectorLayerSelectionSource {\n return new VectorLayerSelectionSourceImpl(\n options.id,\n options.vectorLayer,\n options.label,\n this.#currentIntl\n );\n }\n}\n"],"names":[],"mappings":";;AAYO,MAAM,4BAAA,CAA0E;AAAA,EACnF,YAAA;AAAA,EAEA,WAAA,CAAY,EAAE,WAAA,EAAY,EAAgC;AACtD,IAAA,IAAA,CAAK,YAAA,GAAe,WAAA;AAAA,EACxB;AAAA,EAEA,sBAAsB,OAAA,EAAwE;AAC1F,IAAA,OAAO,IAAI,8BAAA;AAAA,MACP,OAAA,CAAQ,EAAA;AAAA,MACR,OAAA,CAAQ,WAAA;AAAA,MACR,OAAA,CAAQ,KAAA;AAAA,MACR,IAAA,CAAK;AAAA,KACT;AAAA,EACJ;AACJ;;;;"}
@@ -1,7 +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 { SelectionResult, SelectionSource } from "./api";
4
+ import { SelectionResult, SelectionSource } from "../api";
5
5
  /**
6
6
  * Properties supported by the {@link Selection} component.
7
7
  */