@smartnet360/svelte-components 0.0.77 → 0.0.78

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.
@@ -27,6 +27,7 @@
27
27
  import RepeaterLabelsLayer from '../features/repeaters/layers/RepeaterLabelsLayer.svelte';
28
28
  import RepeaterFilterControl from '../features/repeaters/controls/RepeaterFilterControl.svelte';
29
29
  import FeatureSettingsControl from '../shared/controls/FeatureSettingsControl.svelte';
30
+ import FeatureSelectionControl from '../shared/controls/FeatureSelectionControl.svelte';
30
31
  import { createSiteStoreContext } from '../features/sites/stores/siteStoreContext.svelte';
31
32
  import { createCellStoreContext } from '../features/cells/stores/cellStoreContext.svelte';
32
33
  import { createRepeaterStoreContext } from '../features/repeaters/stores/repeaterStoreContext.svelte';
@@ -87,6 +88,12 @@
87
88
  alert(`Selected ${siteIds.length} sites:\n${siteIds.join(', ')}`);
88
89
  }
89
90
 
91
+ // Handler for generic feature selection action button
92
+ function handleProcessFeatures(featureIds: string[]) {
93
+ console.log('Process features:', featureIds);
94
+ alert(`Selected ${featureIds.length} features:\n${featureIds.join(', ')}`);
95
+ }
96
+
90
97
  // Cell filter grouping configuration
91
98
  const cellLevel1Options: Array<Exclude<CellGroupingField, 'none'>> = [
92
99
  'tech',
@@ -222,6 +229,17 @@
222
229
  actionButtonLabel="Open Cluster KPIs"
223
230
  />
224
231
 
232
+ <!-- Generic feature selection control - works with any layer -->
233
+ <FeatureSelectionControl
234
+ position="bottom-left"
235
+ title="Any Feature"
236
+ icon="cursor-fill"
237
+ iconOnlyWhenCollapsed={useIconHeaders}
238
+ onAction={handleProcessFeatures}
239
+ actionButtonLabel="Process Features"
240
+ featureIcon="pin-map-fill"
241
+ />
242
+
225
243
  <!-- Unified feature settings control - Sites, Cells, and Repeaters -->
226
244
  <FeatureSettingsControl
227
245
  siteStore={siteStore}
@@ -5,7 +5,7 @@
5
5
  * Each feature (sites, cells) is completely independent with its own store, layers, and controls.
6
6
  */
7
7
  export { type MapStore, MAP_CONTEXT_KEY, MapboxProvider, ViewportSync, MapStoreBridge, MapStyleControl, createMapStore, createViewportStore, type ViewportStore, type ViewportState, useMapbox, tryUseMapbox } from './core';
8
- export { MapControl, FeatureSettingsControl, addSourceIfMissing, removeSourceIfExists, addLayerIfMissing, removeLayerIfExists, updateGeoJSONSource, removeLayerAndSource, isStyleLoaded, waitForStyleLoad, setFeatureState, removeFeatureState, generateLayerId, generateSourceId } from './shared';
8
+ export { MapControl, FeatureSettingsControl, FeatureSelectionControl, createFeatureSelectionStore, FeatureSelectionStore, type SelectedFeature, addSourceIfMissing, removeSourceIfExists, addLayerIfMissing, removeLayerIfExists, updateGeoJSONSource, removeLayerAndSource, isStyleLoaded, waitForStyleLoad, setFeatureState, removeFeatureState, generateLayerId, generateSourceId } from './shared';
9
9
  export { type Site, type SiteStoreValue, type SiteStoreContext, createSiteStore, createSiteStoreContext, SitesLayer, SiteFilterControl, SiteSelectionControl, SiteSizeSlider, sitesToGeoJSON, siteToFeature, buildSiteTree, getFilteredSites } from './features/sites';
10
10
  export { type Cell, type CellStatus, type CellStatusStyle, type CellGroupingField, type CellGroupingLabels, type CellTreeConfig, type TechnologyBandKey, type CellStoreValue, type CellStoreContext, createCellStoreContext, CellsLayer, CellLabelsLayer, CellFilterControl, CellStyleControl, cellsToGeoJSON, buildCellTree, getFilteredCells, calculateRadius, getZoomFactor, createArcPolygon, DEFAULT_CELL_TREE_CONFIG, TECHNOLOGY_BAND_COLORS, DEFAULT_STATUS_STYLES, RADIUS_MULTIPLIER } from './features/cells';
11
11
  export { type Repeater, type RepeaterTreeNode, type RepeaterStoreValue, type RepeaterStoreContext, createRepeaterStoreContext, RepeatersLayer, RepeaterLabelsLayer, RepeaterFilterControl, repeatersToGeoJSON, buildRepeaterTree, getFilteredRepeaters, getRepeaterRadiusMultiplier, REPEATER_FILL_Z_INDEX, REPEATER_LINE_Z_INDEX, REPEATER_LABEL_Z_INDEX, REPEATER_RADIUS_MULTIPLIER } from './features/repeaters';
@@ -11,7 +11,7 @@ export { MAP_CONTEXT_KEY, MapboxProvider, ViewportSync, MapStoreBridge, MapStyle
11
11
  // ============================================================================
12
12
  // SHARED UTILITIES
13
13
  // ============================================================================
14
- export { MapControl, FeatureSettingsControl, addSourceIfMissing, removeSourceIfExists, addLayerIfMissing, removeLayerIfExists, updateGeoJSONSource, removeLayerAndSource, isStyleLoaded, waitForStyleLoad, setFeatureState, removeFeatureState, generateLayerId, generateSourceId } from './shared';
14
+ export { MapControl, FeatureSettingsControl, FeatureSelectionControl, createFeatureSelectionStore, FeatureSelectionStore, addSourceIfMissing, removeSourceIfExists, addLayerIfMissing, removeLayerIfExists, updateGeoJSONSource, removeLayerAndSource, isStyleLoaded, waitForStyleLoad, setFeatureState, removeFeatureState, generateLayerId, generateSourceId } from './shared';
15
15
  // ============================================================================
16
16
  // SITE FEATURE
17
17
  // ============================================================================
@@ -0,0 +1,342 @@
1
+ <script lang="ts">
2
+ /**
3
+ * FeatureSelectionControl - Layer-agnostic feature selection control
4
+ *
5
+ * Features:
6
+ * - Works with ANY map layer that has features with 'id' property
7
+ * - Toggle to enable/disable click-to-select mode
8
+ * - List of selected feature IDs
9
+ * - Individual delete and clear all
10
+ * - Copy feature IDs to clipboard
11
+ * - Custom action button with callback
12
+ * - Self-contained with its own store
13
+ */
14
+
15
+ import { onMount, onDestroy, getContext } from 'svelte';
16
+ import { get } from 'svelte/store';
17
+ import MapControl from './MapControl.svelte';
18
+ import { createFeatureSelectionStore, type SelectedFeature } from './featureSelectionStore.svelte';
19
+ import { MAP_CONTEXT_KEY, type MapStore } from '../../core/types';
20
+
21
+ interface Props {
22
+ /** Control position */
23
+ position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
24
+ /** Control title */
25
+ title?: string;
26
+ /** Optional header icon */
27
+ icon?: string;
28
+ /** Show icon when collapsed (default: true) */
29
+ iconOnlyWhenCollapsed?: boolean;
30
+ /** Callback when action button clicked */
31
+ onAction?: (featureIds: string[]) => void;
32
+ /** Action button label */
33
+ actionButtonLabel?: string;
34
+ /** Feature icon (default: geo-alt-fill) */
35
+ featureIcon?: string;
36
+ }
37
+
38
+ let {
39
+ position = 'top-left',
40
+ title = 'Feature Selection',
41
+ icon = 'check2-square',
42
+ iconOnlyWhenCollapsed = true,
43
+ onAction,
44
+ actionButtonLabel = 'Process',
45
+ featureIcon = 'geo-alt-fill'
46
+ }: Props = $props();
47
+
48
+ // Get map from context
49
+ const mapStore = getContext<MapStore>(MAP_CONTEXT_KEY);
50
+ console.log('[FeatureSelectionControl] mapStore from context:', mapStore);
51
+
52
+ // Create self-contained store
53
+ const store = createFeatureSelectionStore();
54
+ console.log('[FeatureSelectionControl] Store created:', store);
55
+
56
+ let selectedFeatures = $derived(store.getSelectedFeatures());
57
+ let selectionCount = $derived(store.count);
58
+ let hasSelection = $derived(selectionCount > 0);
59
+
60
+ // Subscribe to map store and initialize when map becomes available
61
+ let unsubscribe: (() => void) | null = null;
62
+
63
+ onMount(() => {
64
+ console.log('[FeatureSelectionControl] Component mounted');
65
+
66
+ // Subscribe to map store to get notified when map is ready
67
+ unsubscribe = mapStore.subscribe((map) => {
68
+ console.log('[FeatureSelectionControl] Map store updated:', map);
69
+ if (map && !store['map']) {
70
+ console.log('[FeatureSelectionControl] Setting map on selection store');
71
+ store.setMap(map);
72
+ }
73
+ });
74
+ });
75
+
76
+ onDestroy(() => {
77
+ // Unsubscribe from map store
78
+ if (unsubscribe) {
79
+ unsubscribe();
80
+ }
81
+ // Cleanup event handlers
82
+ store.destroy();
83
+ });
84
+
85
+ function handleToggleMode() {
86
+ store.toggleSelectionMode();
87
+ }
88
+
89
+ function handleRemoveFeature(featureId: string) {
90
+ store.removeFeatureSelection(featureId);
91
+ }
92
+
93
+ function handleClearAll() {
94
+ store.clearSelection();
95
+ }
96
+
97
+ async function handleCopy() {
98
+ const ids = store.getSelectedIds().join(',');
99
+ try {
100
+ await navigator.clipboard.writeText(ids);
101
+ // Optional: Show toast notification
102
+ } catch (err) {
103
+ console.error('Failed to copy:', err);
104
+ }
105
+ }
106
+
107
+ function handleAction() {
108
+ if (onAction && hasSelection) {
109
+ const ids = store.getSelectedIds();
110
+ onAction(ids);
111
+ }
112
+ }
113
+ </script>
114
+
115
+ <MapControl {position} {title} {icon} {iconOnlyWhenCollapsed} collapsible={true}>
116
+ <div class="feature-selection-control">
117
+ <!-- Selection Mode Toggle -->
118
+ <div class="selection-mode-toggle mb-3">
119
+ <div class="form-check form-switch">
120
+ <input
121
+ type="checkbox"
122
+ class="form-check-input"
123
+ id="feature-selection-mode-toggle"
124
+ checked={store.selectionMode}
125
+ onchange={handleToggleMode}
126
+ />
127
+ <label class="form-check-label" for="feature-selection-mode-toggle">
128
+ Selection Mode
129
+ <span class="badge ms-2" class:bg-success={store.selectionMode} class:bg-secondary={!store.selectionMode}>
130
+ {store.selectionMode ? 'ON' : 'OFF'}
131
+ </span>
132
+ </label>
133
+ </div>
134
+ {#if store.selectionMode}
135
+ <small class="text-muted d-block mt-1">
136
+ Click any feature on the map to select
137
+ </small>
138
+ {/if}
139
+ </div>
140
+
141
+ <!-- Selection Stats -->
142
+ <div class="selection-stats mb-2">
143
+ <strong>{selectionCount}</strong>
144
+ {selectionCount === 1 ? 'feature' : 'features'} selected
145
+ </div>
146
+
147
+ <!-- Action Buttons -->
148
+ {#if hasSelection}
149
+ <div class="action-buttons mb-3">
150
+ <div class="btn-group w-100" role="group">
151
+ <button
152
+ type="button"
153
+ class="btn btn-sm btn-outline-danger"
154
+ onclick={handleClearAll}
155
+ title="Clear all"
156
+ >
157
+ <i class="bi bi-trash"></i> Clear
158
+ </button>
159
+ <button
160
+ type="button"
161
+ class="btn btn-sm btn-outline-secondary"
162
+ onclick={handleCopy}
163
+ title="Copy feature IDs"
164
+ >
165
+ <i class="bi bi-clipboard"></i> Copy
166
+ </button>
167
+ </div>
168
+ </div>
169
+ {/if}
170
+
171
+ <!-- Feature List -->
172
+ {#if hasSelection}
173
+ <div class="feature-list">
174
+ {#each selectedFeatures as feature (feature.id)}
175
+ <div class="feature-item">
176
+ <i class="bi bi-{featureIcon} feature-icon"></i>
177
+ <div class="feature-info">
178
+ <span class="feature-id">{feature.id}</span>
179
+ {#if feature.layerId}
180
+ <small class="feature-layer text-muted">{feature.layerId}</small>
181
+ {/if}
182
+ </div>
183
+ <button
184
+ type="button"
185
+ class="btn-remove"
186
+ onclick={() => handleRemoveFeature(feature.id)}
187
+ title="Remove"
188
+ aria-label="Remove {feature.id}"
189
+ >
190
+ <i class="bi bi-x"></i>
191
+ </button>
192
+ </div>
193
+ {/each}
194
+ </div>
195
+ {:else}
196
+ <div class="text-muted small text-center py-2">
197
+ <i class="bi bi-inbox"></i>
198
+ <div class="mt-1">No features selected</div>
199
+ </div>
200
+ {/if}
201
+
202
+ <!-- Action Button -->
203
+ {#if onAction}
204
+ <div class="mt-3">
205
+ <button
206
+ type="button"
207
+ class="btn btn-primary w-100"
208
+ disabled={!hasSelection}
209
+ onclick={handleAction}
210
+ >
211
+ <i class="bi bi-lightning-charge-fill"></i> {actionButtonLabel}
212
+ </button>
213
+ </div>
214
+ {/if}
215
+ </div>
216
+ </MapControl>
217
+
218
+ <style>
219
+ .feature-selection-control {
220
+ width: 100%;
221
+ min-width: 250px;
222
+ }
223
+
224
+ .selection-mode-toggle {
225
+ padding-bottom: 0.75rem;
226
+ border-bottom: 1px solid #dee2e6;
227
+ }
228
+
229
+ .selection-stats {
230
+ font-size: 0.875rem;
231
+ color: #495057;
232
+ }
233
+
234
+ .action-buttons {
235
+ display: flex;
236
+ gap: 0.5rem;
237
+ }
238
+
239
+ .feature-list {
240
+ max-height: 300px;
241
+ overflow-y: auto;
242
+ border: 1px solid #dee2e6;
243
+ border-radius: 4px;
244
+ padding: 0.5rem;
245
+ }
246
+
247
+ .feature-item {
248
+ display: flex;
249
+ align-items: center;
250
+ gap: 0.5rem;
251
+ padding: 0.5rem;
252
+ border-bottom: 1px solid #f1f3f5;
253
+ transition: background-color 0.15s;
254
+ }
255
+
256
+ .feature-item:last-child {
257
+ border-bottom: none;
258
+ }
259
+
260
+ .feature-item:hover {
261
+ background-color: #f8f9fa;
262
+ }
263
+
264
+ .feature-icon {
265
+ font-size: 1rem;
266
+ flex-shrink: 0;
267
+ color: #0d6efd;
268
+ }
269
+
270
+ .feature-info {
271
+ flex: 1;
272
+ display: flex;
273
+ flex-direction: column;
274
+ gap: 0.125rem;
275
+ min-width: 0;
276
+ }
277
+
278
+ .feature-id {
279
+ font-family: 'Monaco', 'Courier New', monospace;
280
+ font-size: 0.875rem;
281
+ color: #212529;
282
+ white-space: nowrap;
283
+ overflow: hidden;
284
+ text-overflow: ellipsis;
285
+ }
286
+
287
+ .feature-layer {
288
+ font-size: 0.75rem;
289
+ white-space: nowrap;
290
+ overflow: hidden;
291
+ text-overflow: ellipsis;
292
+ }
293
+
294
+ .btn-remove {
295
+ background: none;
296
+ border: none;
297
+ color: #6c757d;
298
+ font-size: 1.25rem;
299
+ line-height: 1;
300
+ padding: 0;
301
+ width: 24px;
302
+ height: 24px;
303
+ display: flex;
304
+ align-items: center;
305
+ justify-content: center;
306
+ cursor: pointer;
307
+ border-radius: 4px;
308
+ transition: all 0.15s;
309
+ flex-shrink: 0;
310
+ }
311
+
312
+ .btn-remove:hover {
313
+ background-color: #ffe6e6;
314
+ color: #dc3545;
315
+ }
316
+
317
+ .btn-remove:active {
318
+ background-color: #ffcccc;
319
+ }
320
+
321
+ /* Ensure primary action button keeps Bootstrap styling inside Mapbox control */
322
+ .feature-selection-control .btn-primary {
323
+ background-color: var(--bs-btn-bg, var(--bs-primary));
324
+ border-color: var(--bs-btn-border-color, var(--bs-primary));
325
+ color: var(--bs-btn-color, var(--bs-body-color));
326
+ }
327
+
328
+ .feature-selection-control .btn-primary:hover,
329
+ .feature-selection-control .btn-primary:focus {
330
+ background-color: var(--bs-btn-hover-bg, var(--bs-primary));
331
+ border-color: var(--bs-btn-hover-border-color, var(--bs-primary));
332
+ color: var(--bs-btn-hover-color, var(--bs-btn-color, var(--bs-body-color)));
333
+ }
334
+
335
+ .feature-selection-control .btn-primary:disabled,
336
+ .feature-selection-control .btn-primary:disabled:hover {
337
+ background-color: var(--bs-btn-disabled-bg, var(--bs-btn-bg, var(--bs-primary)));
338
+ border-color: var(--bs-btn-disabled-border-color, var(--bs-btn-border-color, var(--bs-primary)));
339
+ color: var(--bs-btn-disabled-color, var(--bs-btn-color, var(--bs-body-color)));
340
+ opacity: var(--bs-btn-disabled-opacity, 0.65);
341
+ }
342
+ </style>
@@ -0,0 +1,19 @@
1
+ interface Props {
2
+ /** Control position */
3
+ position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
4
+ /** Control title */
5
+ title?: string;
6
+ /** Optional header icon */
7
+ icon?: string;
8
+ /** Show icon when collapsed (default: true) */
9
+ iconOnlyWhenCollapsed?: boolean;
10
+ /** Callback when action button clicked */
11
+ onAction?: (featureIds: string[]) => void;
12
+ /** Action button label */
13
+ actionButtonLabel?: string;
14
+ /** Feature icon (default: geo-alt-fill) */
15
+ featureIcon?: string;
16
+ }
17
+ declare const FeatureSelectionControl: import("svelte").Component<Props, {}, "">;
18
+ type FeatureSelectionControl = ReturnType<typeof FeatureSelectionControl>;
19
+ export default FeatureSelectionControl;
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Feature Selection Store - Generic layer-agnostic feature selection
3
+ *
4
+ * Manages selection of any map features that have an 'id' property.
5
+ * Self-contained store that can be used independently of specific layers.
6
+ */
7
+ import type { Map as MapboxMap } from 'mapbox-gl';
8
+ export interface SelectedFeature {
9
+ id: string;
10
+ layerId?: string;
11
+ properties?: Record<string, any>;
12
+ }
13
+ export declare class FeatureSelectionStore {
14
+ private selectedFeatures;
15
+ private map;
16
+ selectionMode: boolean;
17
+ private clickHandler;
18
+ constructor();
19
+ /**
20
+ * Initialize the store with a map instance
21
+ */
22
+ setMap(mapInstance: MapboxMap): void;
23
+ /**
24
+ * Setup global click handler for any feature
25
+ */
26
+ private setupClickHandler;
27
+ /**
28
+ * Toggle selection mode on/off
29
+ */
30
+ toggleSelectionMode(): void;
31
+ /**
32
+ * Enable selection mode
33
+ */
34
+ enableSelectionMode(): void;
35
+ /**
36
+ * Disable selection mode
37
+ */
38
+ disableSelectionMode(): void;
39
+ /**
40
+ * Toggle a feature in the selection
41
+ */
42
+ toggleFeatureSelection(id: string, layerId?: string, properties?: Record<string, any>): void;
43
+ /**
44
+ * Add a feature to the selection
45
+ */
46
+ addFeatureSelection(id: string, layerId?: string, properties?: Record<string, any>): void;
47
+ /**
48
+ * Remove a feature from the selection
49
+ */
50
+ removeFeatureSelection(id: string): void;
51
+ /**
52
+ * Clear all selections
53
+ */
54
+ clearSelection(): void;
55
+ /**
56
+ * Get all selected features
57
+ */
58
+ getSelectedFeatures(): SelectedFeature[];
59
+ /**
60
+ * Get selected feature IDs only
61
+ */
62
+ getSelectedIds(): string[];
63
+ /**
64
+ * Check if a feature is selected
65
+ */
66
+ isFeatureSelected(id: string): boolean;
67
+ /**
68
+ * Get selection count
69
+ */
70
+ get count(): number;
71
+ /**
72
+ * Cleanup - remove event handlers
73
+ */
74
+ destroy(): void;
75
+ }
76
+ /**
77
+ * Factory function to create a new feature selection store
78
+ */
79
+ export declare function createFeatureSelectionStore(): FeatureSelectionStore;
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Feature Selection Store - Generic layer-agnostic feature selection
3
+ *
4
+ * Manages selection of any map features that have an 'id' property.
5
+ * Self-contained store that can be used independently of specific layers.
6
+ */
7
+ export class FeatureSelectionStore {
8
+ selectedFeatures = $state([]);
9
+ map = $state(null);
10
+ selectionMode = $state(false);
11
+ clickHandler = null;
12
+ constructor() { }
13
+ /**
14
+ * Initialize the store with a map instance
15
+ */
16
+ setMap(mapInstance) {
17
+ console.log('[FeatureSelection] setMap called with:', mapInstance);
18
+ this.map = mapInstance;
19
+ this.setupClickHandler();
20
+ }
21
+ /**
22
+ * Setup global click handler for any feature
23
+ */
24
+ setupClickHandler() {
25
+ if (!this.map)
26
+ return;
27
+ this.clickHandler = (e) => {
28
+ console.log('[FeatureSelection] Map clicked, selectionMode:', this.selectionMode);
29
+ if (!this.selectionMode)
30
+ return;
31
+ // Query all rendered features at the click point
32
+ const features = this.map.queryRenderedFeatures(e.point);
33
+ console.log('[FeatureSelection] Features found:', features?.length || 0);
34
+ if (features && features.length > 0) {
35
+ // Get the topmost feature with an id
36
+ for (const feature of features) {
37
+ console.log('[FeatureSelection] Feature:', {
38
+ layer: feature.layer?.id,
39
+ properties: feature.properties,
40
+ id: feature.id
41
+ });
42
+ const featureId = feature.properties?.id || feature.id;
43
+ if (featureId) {
44
+ console.log('[FeatureSelection] Found feature with ID:', featureId);
45
+ this.toggleFeatureSelection(String(featureId), feature.layer?.id, feature.properties || undefined);
46
+ break; // Only select the topmost feature
47
+ }
48
+ else {
49
+ console.log('[FeatureSelection] Feature has no id property');
50
+ }
51
+ }
52
+ }
53
+ };
54
+ console.log('[FeatureSelection] Click handler registered on map');
55
+ this.map.on('click', this.clickHandler);
56
+ }
57
+ /**
58
+ * Toggle selection mode on/off
59
+ */
60
+ toggleSelectionMode() {
61
+ this.selectionMode = !this.selectionMode;
62
+ console.log('[FeatureSelection] Selection mode toggled to:', this.selectionMode);
63
+ }
64
+ /**
65
+ * Enable selection mode
66
+ */
67
+ enableSelectionMode() {
68
+ this.selectionMode = true;
69
+ }
70
+ /**
71
+ * Disable selection mode
72
+ */
73
+ disableSelectionMode() {
74
+ this.selectionMode = false;
75
+ }
76
+ /**
77
+ * Toggle a feature in the selection
78
+ */
79
+ toggleFeatureSelection(id, layerId, properties) {
80
+ const index = this.selectedFeatures.findIndex(f => f.id === id);
81
+ if (index >= 0) {
82
+ // Remove if already selected
83
+ this.selectedFeatures.splice(index, 1);
84
+ }
85
+ else {
86
+ // Add if not selected
87
+ this.selectedFeatures.push({ id, layerId, properties });
88
+ }
89
+ }
90
+ /**
91
+ * Add a feature to the selection
92
+ */
93
+ addFeatureSelection(id, layerId, properties) {
94
+ const exists = this.selectedFeatures.some(f => f.id === id);
95
+ if (!exists) {
96
+ this.selectedFeatures.push({ id, layerId, properties });
97
+ }
98
+ }
99
+ /**
100
+ * Remove a feature from the selection
101
+ */
102
+ removeFeatureSelection(id) {
103
+ const index = this.selectedFeatures.findIndex(f => f.id === id);
104
+ if (index >= 0) {
105
+ this.selectedFeatures.splice(index, 1);
106
+ }
107
+ }
108
+ /**
109
+ * Clear all selections
110
+ */
111
+ clearSelection() {
112
+ this.selectedFeatures = [];
113
+ }
114
+ /**
115
+ * Get all selected features
116
+ */
117
+ getSelectedFeatures() {
118
+ return this.selectedFeatures;
119
+ }
120
+ /**
121
+ * Get selected feature IDs only
122
+ */
123
+ getSelectedIds() {
124
+ return this.selectedFeatures.map(f => f.id);
125
+ }
126
+ /**
127
+ * Check if a feature is selected
128
+ */
129
+ isFeatureSelected(id) {
130
+ return this.selectedFeatures.some(f => f.id === id);
131
+ }
132
+ /**
133
+ * Get selection count
134
+ */
135
+ get count() {
136
+ return this.selectedFeatures.length;
137
+ }
138
+ /**
139
+ * Cleanup - remove event handlers
140
+ */
141
+ destroy() {
142
+ if (this.map && this.clickHandler) {
143
+ this.map.off('click', this.clickHandler);
144
+ }
145
+ this.clearSelection();
146
+ this.map = null;
147
+ }
148
+ }
149
+ /**
150
+ * Factory function to create a new feature selection store
151
+ */
152
+ export function createFeatureSelectionStore() {
153
+ return new FeatureSelectionStore();
154
+ }
@@ -5,4 +5,7 @@
5
5
  */
6
6
  export { default as MapControl } from './controls/MapControl.svelte';
7
7
  export { default as FeatureSettingsControl } from './controls/FeatureSettingsControl.svelte';
8
+ export { default as FeatureSelectionControl } from './controls/FeatureSelectionControl.svelte';
9
+ export { createFeatureSelectionStore, FeatureSelectionStore } from './controls/featureSelectionStore.svelte';
10
+ export type { SelectedFeature } from './controls/featureSelectionStore.svelte';
8
11
  export { addSourceIfMissing, removeSourceIfExists, addLayerIfMissing, removeLayerIfExists, updateGeoJSONSource, removeLayerAndSource, isStyleLoaded, waitForStyleLoad, setFeatureState, removeFeatureState, generateLayerId, generateSourceId } from './utils/mapboxHelpers';
@@ -6,5 +6,8 @@
6
6
  // Controls
7
7
  export { default as MapControl } from './controls/MapControl.svelte';
8
8
  export { default as FeatureSettingsControl } from './controls/FeatureSettingsControl.svelte';
9
+ export { default as FeatureSelectionControl } from './controls/FeatureSelectionControl.svelte';
10
+ // Feature Selection Store
11
+ export { createFeatureSelectionStore, FeatureSelectionStore } from './controls/featureSelectionStore.svelte';
9
12
  // Mapbox Helpers
10
13
  export { addSourceIfMissing, removeSourceIfExists, addLayerIfMissing, removeLayerIfExists, updateGeoJSONSource, removeLayerAndSource, isStyleLoaded, waitForStyleLoad, setFeatureState, removeFeatureState, generateLayerId, generateSourceId } from './utils/mapboxHelpers';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartnet360/svelte-components",
3
- "version": "0.0.77",
3
+ "version": "0.0.78",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run prepack",