@emuanalytics/flow-rdf-core 4.0.11 → 4.0.13

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 (29) hide show
  1. package/dist/index.d.mts +2 -2
  2. package/dist/index.d.ts +2 -2
  3. package/dist/lib/map/flow-core-map.mjs +36 -12
  4. package/dist/lib/map/plugins/annotations/annotation.events.mjs +9 -0
  5. package/dist/lib/map/plugins/annotations/annotation.plugin.mjs +134 -2
  6. package/dist/lib/map/plugins/data-join.plugin.mjs +42 -1
  7. package/dist/lib/map/plugins/export/export.plugin.mjs +14 -0
  8. package/dist/lib/map/plugins/export/map-exporter.mjs +37 -34
  9. package/dist/lib/map/plugins/hover-effect.plugin.mjs +9 -0
  10. package/dist/lib/map/plugins/index.d.mts +742 -33
  11. package/dist/lib/map/plugins/index.d.ts +742 -33
  12. package/dist/lib/map/plugins/index.mjs +0 -1
  13. package/dist/lib/map/plugins/keyboard-shortcut-manager.mjs +6 -0
  14. package/dist/lib/map/plugins/map-plugin-base.mjs +5 -0
  15. package/dist/lib/map/plugins/plugin-manager.mjs +97 -0
  16. package/dist/lib/map/plugins/popup.plugin.mjs +12 -0
  17. package/dist/lib/map/plugins/selection/selection.events.mjs +3 -0
  18. package/dist/lib/map/plugins/selection/selection.plugin.mjs +75 -4
  19. package/dist/lib/map/plugins/selection/source-selection.mjs +50 -2
  20. package/dist/lib/map/support/map-events.mjs +19 -0
  21. package/dist/lib/map/support/map-popup.mjs +30 -5
  22. package/dist/lib/utils/index.d.mts +1 -0
  23. package/dist/lib/utils/index.d.ts +1 -0
  24. package/dist/package.json.mjs +1 -1
  25. package/dist/shared/{flow-rdf-core.08156446.d.mts → flow-rdf-core.3da5bd49.d.mts} +448 -17
  26. package/dist/shared/{flow-rdf-core.08156446.d.ts → flow-rdf-core.3da5bd49.d.ts} +448 -17
  27. package/package.json +4 -3
  28. package/styles/flow-rdf-core.css +1 -1
  29. package/dist/lib/map/plugins/url-sync.plugin.mjs +0 -55
@@ -8,4 +8,3 @@ export { PluginManager } from './plugin-manager.mjs';
8
8
  export { PopupPlugin } from './popup.plugin.mjs';
9
9
  export { SelectionEvent } from './selection/selection.events.mjs';
10
10
  export { SelectionPlugin } from './selection/selection.plugin.mjs';
11
- export { UrlSyncPlugin } from './url-sync.plugin.mjs';
@@ -91,6 +91,9 @@ class KeyboardShortcutManager {
91
91
  removeFromListeners(listenerToRemove) {
92
92
  this.listeners = this.listeners.filter((listener) => listener !== listenerToRemove);
93
93
  }
94
+ /**
95
+ * Converts a key definition string into a normalized key definition.
96
+ */
94
97
  normalizeKeyDefinition(keyDef) {
95
98
  const parts = keyDef.split("+");
96
99
  const ev = {
@@ -102,6 +105,9 @@ class KeyboardShortcutManager {
102
105
  };
103
106
  return this.keyDefinitionFromEvent(ev);
104
107
  }
108
+ /**
109
+ * Converts a keyboard event into a key definition string.
110
+ */
105
111
  keyDefinitionFromEvent(ev) {
106
112
  let keyDef = "";
107
113
  if (ev.ctrlKey)
@@ -7,6 +7,7 @@ var __publicField = (obj, key, value) => {
7
7
  return value;
8
8
  };
9
9
  class MapPluginBase extends EventEmitter {
10
+ /** Constructs an plugin instance. Note this this is called internally when registering a plugin. */
10
11
  constructor(pluginManager, options) {
11
12
  super();
12
13
  __publicField(this, "_pluginManager");
@@ -14,9 +15,13 @@ class MapPluginBase extends EventEmitter {
14
15
  this._pluginManager = pluginManager;
15
16
  this.options = options ?? {};
16
17
  }
18
+ /**
19
+ * The map instance associated with this plugin.
20
+ */
17
21
  get map() {
18
22
  return this._pluginManager.map;
19
23
  }
24
+ /** The {@link PluginManager} that is managing this plugin. */
20
25
  get pluginManager() {
21
26
  return this._pluginManager;
22
27
  }
@@ -12,6 +12,10 @@ var __publicField = (obj, key, value) => {
12
12
  };
13
13
  const debug = Debug("flow:map-plugin-manager");
14
14
  class PluginManager {
15
+ /**
16
+ * Constructs a plugin manager for the provided FlowCoreMap instance. Note that plugin manager construction is performed automatically when a
17
+ * map is created.
18
+ */
15
19
  constructor(map) {
16
20
  __publicField(this, "_map");
17
21
  __publicField(this, "_keyboardShortcutManager", new KeyboardShortcutManager());
@@ -28,9 +32,27 @@ class PluginManager {
28
32
  }
29
33
  });
30
34
  }
35
+ /** Returns the parent FlowCoreMap instance. */
31
36
  get map() {
32
37
  return this._map;
33
38
  }
39
+ /**
40
+ * Register a plugin for use with a map. This will create an instance of the plugin and call its `initialize` method.
41
+ *
42
+ * Note that this is typically called using the `usePlugin` method of a {@link FlowCoreMap} instance.
43
+ *
44
+ * @example
45
+ * Register the HoverEffectPlugin for use with a map:
46
+ ```js
47
+ import { HoverEffectPlugin } from '@emuanalytics/flow-rdf-core/plugins';
48
+ map.usePlugin(HoverEffectPlugin);
49
+ ```
50
+ *
51
+ * @param plugin The plugin constructor to register. This must be a class that extends {@link MapPluginBase}
52
+ * @param options Plugin-specific options (if any) to pass to the plugin constructor
53
+ * @typeParam T Type of the plugin being registered
54
+ * @returns The registered plugin instance
55
+ */
34
56
  usePlugin(plugin, options) {
35
57
  if (this._plugins.has(plugin)) {
36
58
  const oldInstance = this._plugins.get(plugin);
@@ -45,6 +67,22 @@ class PluginManager {
45
67
  pluginInstance.initialize();
46
68
  return pluginInstance;
47
69
  }
70
+ /**
71
+ * Retrieve a plugin previously registered with a map. This is necessary to access plugin-specific methods or properties.
72
+ *
73
+ * Note that this is typically called using the {@link FlowCoreMap.getPlugin} method.
74
+ *
75
+ * @example
76
+ * Retrieve the SelectionPlugin and clear all selections for a specified map data source:
77
+ ```js
78
+ const selectionPlugin = map.getPlugin(SelectionPlugin);
79
+ selectionPlugin.selectFeatures('data-source', []);
80
+ ```
81
+ *
82
+ * @param plugin The plugin to retrieve (identified by the plugin constructor or name)
83
+ * @typeParam T Type of the plugin being retrieved
84
+ * @returns The registered plugin instance
85
+ */
48
86
  getPlugin(pluginOrName) {
49
87
  let name;
50
88
  let pluginInstance;
@@ -60,6 +98,13 @@ class PluginManager {
60
98
  }
61
99
  return pluginInstance;
62
100
  }
101
+ /**
102
+ * Unregister the specified plugin.
103
+ *
104
+ * @param pluginOrName The plugin to unregister (identified by the plugin constructor or name)
105
+ * @returns The plugin manager instance to allow method chaining
106
+ * @typeParam T Type of the plugin being unregistered
107
+ */
63
108
  removePlugin(pluginOrName) {
64
109
  let pluginConstructor;
65
110
  let pluginInstance;
@@ -78,6 +123,11 @@ class PluginManager {
78
123
  this._plugins.delete(pluginConstructor);
79
124
  return this;
80
125
  }
126
+ /**
127
+ * Unregister all plugins.
128
+ *
129
+ * @returns The plugin manager instance to allow method chaining
130
+ */
81
131
  removeAllPlugins() {
82
132
  this._plugins.forEach((plugin) => {
83
133
  if (plugin.destroy)
@@ -86,19 +136,58 @@ class PluginManager {
86
136
  this._plugins.clear();
87
137
  return this;
88
138
  }
139
+ /**
140
+ * Mark the specified plugin as 'exclusive'. This disables other non-exclusive plugins until the {@link removeExclusivePlugin} method is called.
141
+ *
142
+ * This is used by the annotation plugin to disable map selection and hover effects when annotation features are being drawn by the user.
143
+ */
89
144
  setExclusivePlugin(plugin) {
90
145
  this._exclusivePlugins.add(plugin);
91
146
  this.forwardSetExclusivePlugin();
92
147
  return this;
93
148
  }
149
+ /**
150
+ * Remove the 'exclusive' flag from the specified plugin. This allows other non-exclusive plugins to receive events again.
151
+ */
94
152
  removeExclusivePlugin(plugin) {
95
153
  this._exclusivePlugins.delete(plugin);
96
154
  this.forwardSetExclusivePlugin();
97
155
  return this;
98
156
  }
157
+ /**
158
+ * This method is called by plugins to register keyboard shortcuts with the {@link KeyboardShortcutManager}.
159
+ *
160
+ * @example
161
+ * Register keyboard shortcuts from a map plugin:
162
+ ```js
163
+ // Add shortcuts
164
+ this.removeShortcuts = this.pluginManager.addKeyboardShortcuts({
165
+ 'Enter,Escape': () => this.commit(),
166
+ KeyP: () => this.createFeature('Polygon'),
167
+ KeyL: () => this.createFeature('LineString'),
168
+ KeyM: () => this.createFeature('Point'),
169
+ KeyC: () => this.createFeature('Circle')
170
+ });
171
+
172
+ ...
173
+
174
+ // Remove shortcuts
175
+ this.removeShortcuts();
176
+ ```
177
+ *
178
+ * @param keyboardShortcuts Key bindings to register
179
+ * @param priority Key binding priority
180
+ * @returns Function that can be called to remove the keyboard shortcuts
181
+ */
99
182
  addKeyboardShortcuts(keyboardShortcuts, priority = 0) {
100
183
  return this._keyboardShortcutManager.addListener(keyboardShortcuts, priority);
101
184
  }
185
+ /**
186
+ * This method is called internally to forward map events to active plugin handlers.
187
+ *
188
+ * @param eventName The event to forward
189
+ * @param args Event arguments
190
+ */
102
191
  forwardEvent(eventName, ...args) {
103
192
  const handlerName = `handle${capitalize(eventName)}`;
104
193
  this._plugins.forEach((plugin) => {
@@ -110,6 +199,11 @@ class PluginManager {
110
199
  }
111
200
  });
112
201
  }
202
+ /**
203
+ * This method is called internally to forward map style change events to active plugin handlers.
204
+ *
205
+ * @param eventName The event to forward
206
+ */
113
207
  forwardStyleChangeEvent(event) {
114
208
  this._plugins.forEach((plugin) => {
115
209
  if (this._exclusivePlugins.size && !this._exclusivePlugins.has(plugin))
@@ -123,6 +217,9 @@ class PluginManager {
123
217
  });
124
218
  return event.newStyle;
125
219
  }
220
+ /**
221
+ * This method is called internally to forward exclusivity changes to registered plugins.
222
+ */
126
223
  forwardSetExclusivePlugin() {
127
224
  this._plugins.forEach((plugin) => {
128
225
  if (plugin.handleSetExclusivePlugin) {
@@ -11,6 +11,12 @@ var __publicField = (obj, key, value) => {
11
11
  return value;
12
12
  };
13
13
  class PopupPlugin extends MapPluginBase {
14
+ /**
15
+ * Constructs a new PopupPlugin instance. This is called automatically when the plugin is added to a {@link FlowCoreMap} instance
16
+ * using {@link FlowCoreMap.usePlugin} method.
17
+ *
18
+ * @param pluginManager The plugin manager instance associated with the map
19
+ */
14
20
  constructor(pluginManager) {
15
21
  super(pluginManager);
16
22
  __publicField(this, "_popupTemplates", /* @__PURE__ */ new Map());
@@ -37,10 +43,16 @@ class PopupPlugin extends MapPluginBase {
37
43
  }
38
44
  return popup;
39
45
  }
46
+ /**
47
+ * Closes all open popups.
48
+ */
40
49
  closeAllPopups() {
41
50
  this._popups.forEach((popup) => popup.close());
42
51
  this._popups.clear();
43
52
  }
53
+ /**
54
+ * Closes open popups for the specified {@link MapDataSource}s.
55
+ */
44
56
  closePopupsForSources(sources) {
45
57
  sources = ensureArray(sources);
46
58
  this._popups.forEach((popup) => {
@@ -9,8 +9,11 @@ var __publicField = (obj, key, value) => {
9
9
  class SelectionEvent extends MapEvent {
10
10
  constructor(type, map, sourceIdentifier, features) {
11
11
  super(type, map);
12
+ /** Map data source ID */
12
13
  __publicField(this, "source");
14
+ /** Map data source layer ID */
13
15
  __publicField(this, "sourceLayer");
16
+ /** Array of selected feature IDs */
14
17
  __publicField(this, "features");
15
18
  this.source = sourceIdentifier.source;
16
19
  this.sourceLayer = sourceIdentifier.sourceLayer;
@@ -12,12 +12,23 @@ var __publicField = (obj, key, value) => {
12
12
  return value;
13
13
  };
14
14
  class SelectionPlugin extends MapPluginBase {
15
+ /**
16
+ * Constructs a new SelectionPlugin instance. This is called automatically when the plugin is added to a {@link FlowCoreMap} instance
17
+ * using {@link FlowCoreMap.usePlugin} method.
18
+ *
19
+ * @param pluginManager The plugin manager instance associated with the map
20
+ */
15
21
  constructor(pluginManager) {
16
22
  super(pluginManager);
17
23
  __publicField(this, "_sourceSelections", /* @__PURE__ */ new Map());
18
24
  __publicField(this, "_enablePopups", true);
19
25
  __publicField(this, "name", "SelectionPlugin");
20
26
  }
27
+ /**
28
+ * Clears selection for the specified map data source.
29
+ *
30
+ * @param source Map data source ID
31
+ */
21
32
  clearSelection(source) {
22
33
  const sourceSelection = this._sourceSelections.get(source);
23
34
  if (!sourceSelection) {
@@ -29,6 +40,12 @@ class SelectionPlugin extends MapPluginBase {
29
40
  sourceSelection.updateSelection(/* @__PURE__ */ new Set());
30
41
  }
31
42
  }
43
+ /**
44
+ * Deselect the specified feature or features.
45
+ *
46
+ * @param source Map data source ID
47
+ * @param features feature ID or array of feature IDs to deselect
48
+ */
32
49
  deselectFeatures(source, features) {
33
50
  const sourceSelection = this._sourceSelections.get(source);
34
51
  if (!sourceSelection) {
@@ -43,16 +60,30 @@ class SelectionPlugin extends MapPluginBase {
43
60
  sourceSelection.updateSelection(currentSelection);
44
61
  }
45
62
  }
63
+ /**
64
+ * @internal
65
+ */
46
66
  destroy() {
47
67
  this._sourceSelections.forEach((sel) => sel.destroy());
48
68
  this._sourceSelections.clear();
49
69
  }
70
+ /**
71
+ * Disable selection for the specified map data source.
72
+ *
73
+ * @param source Map data source ID
74
+ */
50
75
  disableSelection(source) {
51
76
  if (this._sourceSelections.has(source)) {
52
77
  this._sourceSelections.get(source).destroy();
53
78
  this._sourceSelections.delete(source);
54
79
  }
55
80
  }
81
+ /**
82
+ * Enable selection support for the specified map data source.
83
+ *
84
+ * @param source Map data source ID
85
+ * @param options Selection options
86
+ */
56
87
  enableSelection(source, options) {
57
88
  if (options.popup) {
58
89
  try {
@@ -64,12 +95,18 @@ class SelectionPlugin extends MapPluginBase {
64
95
  this.disableSelection(source);
65
96
  this._sourceSelections.set(source, new SourceSelection(this, source, options));
66
97
  }
98
+ /**
99
+ * @internal
100
+ */
67
101
  handleBeforeSourceRemoved(event) {
68
102
  if (this._sourceSelections.has(event.source)) {
69
103
  this._sourceSelections.get(event.source)?.destroy();
70
104
  this._sourceSelections.delete(event.source);
71
105
  }
72
106
  }
107
+ /**
108
+ * @internal
109
+ */
73
110
  handleClick(event) {
74
111
  const multiSelect = event.originalEvent.shiftKey || event.originalEvent.altKey || event.originalEvent.metaKey;
75
112
  const clickedFeature = event.features[0];
@@ -79,7 +116,17 @@ class SelectionPlugin extends MapPluginBase {
79
116
  (sel) => sel.handleClick(event.lngLat, clickedFeature, selectionOptions, multiSelect, this._enablePopups)
80
117
  );
81
118
  }
82
- selectFeatures(source, features) {
119
+ /**
120
+ * Select the specified feature or features.
121
+ *
122
+ * Use this method to programmatically select features on a map data source. This will update the selection state and trigger display of
123
+ * any associated popups if configured.
124
+ *
125
+ * @param source Map data source ID
126
+ * @param features feature ID or array of feature IDs to select
127
+ * @param showPopups Show popups for the selected features. Defaults to `true`.
128
+ */
129
+ selectFeatures(source, features, showPopups = true) {
83
130
  const sourceSelection = this._sourceSelections.get(source);
84
131
  if (!sourceSelection) {
85
132
  return;
@@ -93,11 +140,11 @@ class SelectionPlugin extends MapPluginBase {
93
140
  switch (sourceSelection.options.mode) {
94
141
  case "single":
95
142
  currentSelection = /* @__PURE__ */ new Set([features[0]]);
96
- sourceSelection.updateSelection(currentSelection);
143
+ sourceSelection.updateSelection(currentSelection, showPopups);
97
144
  break;
98
145
  case "multi":
99
146
  features.forEach((f) => currentSelection.add(f));
100
- sourceSelection.updateSelection(currentSelection);
147
+ sourceSelection.updateSelection(currentSelection, showPopups);
101
148
  break;
102
149
  }
103
150
  if (sourceSelection.selectedFeatureIds.size && isStringFull(sourceSelection.options.group)) {
@@ -108,6 +155,12 @@ class SelectionPlugin extends MapPluginBase {
108
155
  });
109
156
  }
110
157
  }
158
+ /**
159
+ * Gets the selected feature IDs for the specified map data source.
160
+ *
161
+ * @param source map data source ID
162
+ * @returns Array of selected feature IDs
163
+ */
111
164
  selectedFeatureIds(source) {
112
165
  const sourceSelection = this._sourceSelections.get(source);
113
166
  if (!sourceSelection) {
@@ -115,6 +168,15 @@ class SelectionPlugin extends MapPluginBase {
115
168
  }
116
169
  return Array.from(sourceSelection.selectedFeatureIds);
117
170
  }
171
+ /**
172
+ * Returns a {@link MapFeatureStream} of selected features for the specified map data source.
173
+ *
174
+ * Subscribe to the stream to receive updates when the selection state changes and receive full feature property data
175
+ * from the map.
176
+ *
177
+ * @param source Map data source ID
178
+ * @returns {@link MapFeatureStream} of selected features
179
+ */
118
180
  selectedFeatureStream(source) {
119
181
  const sourceSelection = this._sourceSelections.get(source);
120
182
  if (!sourceSelection) {
@@ -122,9 +184,18 @@ class SelectionPlugin extends MapPluginBase {
122
184
  }
123
185
  return sourceSelection.selectedFeatureStream;
124
186
  }
125
- enablePopups(enabled = true) {
187
+ /**
188
+ * Enable display of popups when a feature is clicked and an associated popup has been registered with the map data source.
189
+ * See {@link PopupPlugin} for more details of how to define and register popups.
190
+ *
191
+ * Note that popups are enabled by default.
192
+ */
193
+ enablePopups() {
126
194
  this._enablePopups = true;
127
195
  }
196
+ /**
197
+ * Disable display of popups when a feature is clicked. This will also close any currently open popups.
198
+ */
128
199
  disablePopups() {
129
200
  if (this._enablePopups) {
130
201
  this.map.getPlugin(PopupPlugin).closeAllPopups();
@@ -1,3 +1,5 @@
1
+ import turfCentroid from '@turf/centroid';
2
+ import Debug from 'debug';
1
3
  import { assert } from '../../../utils/assert.mjs';
2
4
  import { ensureArray } from '../../../utils/ensure-array.mjs';
3
5
  import { isStringFull } from '../../../utils/type-checks.mjs';
@@ -11,7 +13,9 @@ var __publicField = (obj, key, value) => {
11
13
  __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
12
14
  return value;
13
15
  };
16
+ const debug = Debug("flow:SourceSelection");
14
17
  class SourceSelection {
18
+ /** Constructs a SourceSelection to handle selection for the specified map data source. */
15
19
  constructor(plugin, source, options) {
16
20
  __publicField(this, "_featureStream");
17
21
  __publicField(this, "_map");
@@ -21,6 +25,8 @@ class SourceSelection {
21
25
  __publicField(this, "_selectedFeatureIds", /* @__PURE__ */ new Set());
22
26
  __publicField(this, "_sourceId");
23
27
  __publicField(this, "_stateSubscription", null);
28
+ __publicField(this, "_featuresSubscription", null);
29
+ __publicField(this, "_pendingPopups", []);
24
30
  this._plugin = plugin;
25
31
  this._map = plugin.map;
26
32
  this._options = options;
@@ -34,10 +40,15 @@ class SourceSelection {
34
40
  sourceLayer: options.sourceLayer,
35
41
  featureIds: []
36
42
  });
43
+ if (this._options.popup) {
44
+ this._featuresSubscription = this._featureStream.subscribe(() => {
45
+ this.showPendingPopups();
46
+ });
47
+ }
37
48
  if (options.selectionState) {
38
49
  this._stateSubscription = this._map.context.state.toStream(options.selectionState, true).subscribe((val) => {
39
50
  val = new Set(ensureArray(val));
40
- this.updateSelection(val);
51
+ this.updateSelection(val, true);
41
52
  });
42
53
  }
43
54
  }
@@ -50,12 +61,15 @@ class SourceSelection {
50
61
  get selectedFeatureStream() {
51
62
  return this._featureStream;
52
63
  }
64
+ /** Plugin destroy handler */
53
65
  destroy() {
54
66
  this._popups.forEach((popup) => popup.close());
55
67
  this._stateSubscription?.unsubscribe();
68
+ this._featuresSubscription?.unsubscribe();
56
69
  this._selectedFeatureIds = /* @__PURE__ */ new Set();
57
70
  this.updateMapSelectionState();
58
71
  }
72
+ /** Plugin click handler */
59
73
  handleClick(lngLat, feature, selectionOptions, multiSelect, enablePopups) {
60
74
  if (this._options.mode === "none") {
61
75
  return;
@@ -111,7 +125,13 @@ class SourceSelection {
111
125
  }
112
126
  }
113
127
  }
114
- updateSelection(featureIds) {
128
+ /** Update the current selection. */
129
+ updateSelection(featureIds, showPopups = false) {
130
+ if (showPopups) {
131
+ this._pendingPopups = Array.from(featureIds);
132
+ } else {
133
+ this._pendingPopups = [];
134
+ }
115
135
  if (!this.areSetsEqual(featureIds, this._selectedFeatureIds)) {
116
136
  this._selectedFeatureIds = featureIds;
117
137
  this.updateMapSelectionState();
@@ -131,16 +151,19 @@ class SourceSelection {
131
151
  );
132
152
  this._plugin.emit(event.type, event);
133
153
  }
154
+ this.showPendingPopups();
134
155
  }
135
156
  areSetsEqual(a, b) {
136
157
  return a.size === b.size && [...a].every((value) => b.has(value));
137
158
  }
159
+ /** Sync map feature state with current selection. */
138
160
  updateMapSelectionState() {
139
161
  this._map.removeFeatureState(this._sourceId, "selected", false);
140
162
  this._selectedFeatureIds.forEach(
141
163
  (id) => this._map.setFeatureState({ ...this._sourceId, id }, { selected: true }, false)
142
164
  );
143
165
  }
166
+ /** Sync bound reactive property with current selection. */
144
167
  updateStateStore() {
145
168
  if (isStringFull(this._options.selectionState)) {
146
169
  const selectedIds = Array.from(this._selectedFeatureIds);
@@ -151,6 +174,31 @@ class SourceSelection {
151
174
  }
152
175
  }
153
176
  }
177
+ /**
178
+ * Supports displaying popups after selection has been set programmatically.
179
+ *
180
+ * Opening a selected popup is deferred until the next feature stream update so that
181
+ * feature geometry is available to calculate the popup position.
182
+ *
183
+ * @param features Selected features from the feature stream.
184
+ */
185
+ showPendingPopups() {
186
+ for (const feature of Object.values(this._featureStream.featureData)) {
187
+ assert(isFeatureWithId(feature));
188
+ if (this._pendingPopups.includes(feature.id) && !this._popups.has(feature.id) && this._options.popup) {
189
+ debug(`Showing pending popup for source ${this._sourceId.source}`, feature);
190
+ const lngLat = turfCentroid(feature.geometry).geometry.coordinates;
191
+ const popup = this._map.getPlugin(PopupPlugin).openPopup(this._options.popup, lngLat, feature);
192
+ if (popup) {
193
+ popup.on("closed", () => {
194
+ this._popups.delete(feature.id);
195
+ });
196
+ this._popups.set(feature.id, popup);
197
+ }
198
+ }
199
+ this._pendingPopups = this._pendingPopups.filter((id) => id !== feature.id);
200
+ }
201
+ }
154
202
  }
155
203
 
156
204
  export { SourceSelection };
@@ -5,14 +5,25 @@ var __publicField = (obj, key, value) => {
5
5
  return value;
6
6
  };
7
7
  class MapEvent {
8
+ /**
9
+ * Constructs a map event.
10
+ *
11
+ * @param type Event type
12
+ * @param map Map instance that generated the event
13
+ * @param originalEvent Original MapLibre event that triggered the event
14
+ */
8
15
  constructor(type, map, originalEvent) {
16
+ /** Event type */
9
17
  __publicField(this, "type");
18
+ /** Map instance that generated event */
10
19
  __publicField(this, "map");
20
+ /** Original MapLibre event */
11
21
  __publicField(this, "originalEvent");
12
22
  this.type = type;
13
23
  this.map = map;
14
24
  this.originalEvent = originalEvent;
15
25
  }
26
+ /** Creates an event from an original MapLibre event. */
16
27
  static fromMaplibreEvent(type, map, maplibreEvent) {
17
28
  return new MapEvent(type, map, maplibreEvent.originalEvent);
18
29
  }
@@ -28,6 +39,10 @@ class MapMouseEvent extends MapEvent {
28
39
  * The geographic location on the map of the mouse cursor.
29
40
  */
30
41
  __publicField(this, "lngLat");
42
+ /**
43
+ * Features found at the mouse coordinate. Features are ordered by map layer with features higher in the layer stack appearing first in the `features` array.
44
+ * Features from non-interactive layers are excluded.
45
+ */
31
46
  __publicField(this, "features");
32
47
  this.point = point;
33
48
  this.lngLat = lngLat;
@@ -47,6 +62,7 @@ class MapMouseEvent extends MapEvent {
47
62
  class MapSourceEvent extends MapEvent {
48
63
  constructor(type, map, source) {
49
64
  super(type, map);
65
+ /** Map data source ID */
50
66
  __publicField(this, "source");
51
67
  this.source = source;
52
68
  }
@@ -54,6 +70,7 @@ class MapSourceEvent extends MapEvent {
54
70
  class MapLoadingEvent extends MapEvent {
55
71
  constructor(map, loading) {
56
72
  super("loading", map);
73
+ /** Indicates whether the map is currently loading data. */
57
74
  __publicField(this, "loading");
58
75
  this.loading = loading;
59
76
  }
@@ -61,7 +78,9 @@ class MapLoadingEvent extends MapEvent {
61
78
  class MapStyleChangeEvent extends MapEvent {
62
79
  constructor(type, map, oldStyle, newStyle) {
63
80
  super(type, map);
81
+ /** Old map style before changes. */
64
82
  __publicField(this, "oldStyle");
83
+ /** New map style after changes. */
65
84
  __publicField(this, "newStyle");
66
85
  this.oldStyle = oldStyle;
67
86
  this.newStyle = newStyle;
@@ -16,12 +16,12 @@ var __publicField = (obj, key, value) => {
16
16
  Debug("flow:map-popup");
17
17
  class MapPopup extends EventEmitter {
18
18
  /**
19
- * Constructs a new `MapPopup` instance.
19
+ * Constructs a new {@link MapPopup} instance.
20
20
  *
21
- * This is called by the `FlowMap::openPopup` method.
21
+ * Typically, a popup is created by interacting with the PopupPlugin or SelectionPlugin rather than instantiating a popup directly.
22
22
  *
23
- * @param map Parent Flo.w map.
24
- * @param options Popup options.
23
+ * @param map Parent map
24
+ * @param options Popup options
25
25
  */
26
26
  constructor(map, options) {
27
27
  super();
@@ -44,6 +44,9 @@ class MapPopup extends EventEmitter {
44
44
  ...this.options
45
45
  };
46
46
  }
47
+ /**
48
+ * Get the map feature associated with the popup.
49
+ */
47
50
  get feature() {
48
51
  return this._feature;
49
52
  }
@@ -57,7 +60,7 @@ class MapPopup extends EventEmitter {
57
60
  }
58
61
  }
59
62
  /**
60
- * Move the popup to a new location.
63
+ * Move the popup to the specified location.
61
64
  *
62
65
  * @param lngLat New location.
63
66
  */
@@ -107,6 +110,9 @@ class MapPopup extends EventEmitter {
107
110
  this.renderContent(null);
108
111
  }
109
112
  }
113
+ /**
114
+ * Returns CSS classes to add to the popup.
115
+ */
110
116
  cssClasses() {
111
117
  const classes = ["flow-map-popup"];
112
118
  if (isStringFull(this.options.class)) {
@@ -117,6 +123,14 @@ class MapPopup extends EventEmitter {
117
123
  }
118
124
  return classes;
119
125
  }
126
+ /**
127
+ * Update the popup in response to feature data changes.
128
+ *
129
+ * The popup may be moved or re-rendered depending on the nature of the
130
+ * feature changes.
131
+ *
132
+ * @param feature Map feature associated with the popup
133
+ */
120
134
  handleFeatureUpdate(feature) {
121
135
  if (!feature) {
122
136
  this.close();
@@ -134,6 +148,9 @@ class MapPopup extends EventEmitter {
134
148
  }
135
149
  this.renderContent(feature);
136
150
  }
151
+ /**
152
+ * Called when the popup is closed to clean up resources.
153
+ */
137
154
  popupCloseHandler() {
138
155
  if (this._featureStreamDisposer) {
139
156
  this._featureStreamDisposer();
@@ -142,6 +159,14 @@ class MapPopup extends EventEmitter {
142
159
  this.emit("closed", this);
143
160
  this.removeAllListeners();
144
161
  }
162
+ /**
163
+ * Render the popup content.
164
+ *
165
+ * If the content is defined using a template function, the function is called passing
166
+ * in the associated map feature and the reactive state store.
167
+ *
168
+ * @param feature The map feature associated with the popup.
169
+ */
145
170
  renderContent(feature) {
146
171
  const state = this.map.context.state;
147
172
  const content = isFunction(this.options.content) ? this.options.content(feature, state) : this.options.content;