@smartnet360/svelte-components 0.0.65 → 0.0.67

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 (31) hide show
  1. package/dist/map-v2/core/controls/MapStyleControl.svelte +76 -81
  2. package/dist/map-v2/core/controls/MapStyleControl.svelte.d.ts +4 -0
  3. package/dist/map-v2/demo/DemoMap.svelte +75 -7
  4. package/dist/map-v2/features/cells/controls/CellFilterControl.svelte +14 -5
  5. package/dist/map-v2/features/cells/controls/CellFilterControl.svelte.d.ts +4 -0
  6. package/dist/map-v2/features/cells/controls/CellStyleControl.svelte +8 -2
  7. package/dist/map-v2/features/cells/controls/CellStyleControl.svelte.d.ts +4 -0
  8. package/dist/map-v2/features/cells/layers/CellsLayer.svelte +2 -1
  9. package/dist/map-v2/features/cells/stores/cellStoreContext.svelte.d.ts +3 -0
  10. package/dist/map-v2/features/cells/stores/cellStoreContext.svelte.js +5 -0
  11. package/dist/map-v2/features/cells/utils/cellGeoJSON.d.ts +2 -6
  12. package/dist/map-v2/features/cells/utils/cellGeoJSON.js +11 -13
  13. package/dist/map-v2/features/cells/utils/cellTree.d.ts +5 -2
  14. package/dist/map-v2/features/cells/utils/cellTree.js +17 -6
  15. package/dist/map-v2/features/sites/controls/SiteFilterControl.svelte +8 -2
  16. package/dist/map-v2/features/sites/controls/SiteFilterControl.svelte.d.ts +4 -0
  17. package/dist/map-v2/features/sites/controls/SiteSelectionControl.svelte +281 -0
  18. package/dist/map-v2/features/sites/controls/SiteSelectionControl.svelte.d.ts +20 -0
  19. package/dist/map-v2/features/sites/controls/SiteSizeSlider.svelte +8 -2
  20. package/dist/map-v2/features/sites/controls/SiteSizeSlider.svelte.d.ts +4 -0
  21. package/dist/map-v2/features/sites/index.d.ts +1 -0
  22. package/dist/map-v2/features/sites/index.js +1 -0
  23. package/dist/map-v2/features/sites/layers/SitesLayer.svelte +11 -1
  24. package/dist/map-v2/features/sites/stores/siteStoreContext.svelte.d.ts +7 -0
  25. package/dist/map-v2/features/sites/stores/siteStoreContext.svelte.js +38 -1
  26. package/dist/map-v2/features/sites/types.d.ts +2 -0
  27. package/dist/map-v2/index.d.ts +1 -1
  28. package/dist/map-v2/index.js +1 -1
  29. package/dist/map-v2/shared/controls/MapControl.svelte +41 -4
  30. package/dist/map-v2/shared/controls/MapControl.svelte.d.ts +4 -0
  31. package/package.json +1 -1
@@ -25,6 +25,10 @@
25
25
  position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
26
26
  /** Control title */
27
27
  title?: string;
28
+ /** Optional header icon */
29
+ icon?: string;
30
+ /** Show icon when collapsed (default: true) */
31
+ iconOnlyWhenCollapsed?: boolean;
28
32
  /** Initially collapsed? */
29
33
  initiallyCollapsed?: boolean;
30
34
  }
@@ -33,7 +37,9 @@
33
37
  store,
34
38
  position = 'top-left',
35
39
  title = 'Site Filter',
36
- initiallyCollapsed = false
40
+ icon = 'funnel',
41
+ iconOnlyWhenCollapsed = true,
42
+ initiallyCollapsed = true
37
43
  }: Props = $props();
38
44
 
39
45
  let treeStore = $state<Writable<TreeStoreValue> | null>(null);
@@ -77,7 +83,7 @@
77
83
  }
78
84
  </script>
79
85
 
80
- <MapControl {position} {title} collapsible={true} {initiallyCollapsed}>
86
+ <MapControl {position} {title} {icon} {iconOnlyWhenCollapsed} collapsible={true} {initiallyCollapsed}>
81
87
  {#if treeStore && $treeStore}
82
88
  <div class="site-filter-tree">
83
89
  <TreeView store={$treeStore} showControls={false}>
@@ -6,6 +6,10 @@ interface Props {
6
6
  position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
7
7
  /** Control title */
8
8
  title?: string;
9
+ /** Optional header icon */
10
+ icon?: string;
11
+ /** Show icon when collapsed (default: true) */
12
+ iconOnlyWhenCollapsed?: boolean;
9
13
  /** Initially collapsed? */
10
14
  initiallyCollapsed?: boolean;
11
15
  }
@@ -0,0 +1,281 @@
1
+ <script lang="ts">
2
+ /**
3
+ * SiteSelectionControl - Site selection list builder
4
+ *
5
+ * Features:
6
+ * - Toggle to enable/disable click-to-select mode
7
+ * - List of selected sites
8
+ * - Individual delete and clear all
9
+ * - Copy site IDs to clipboard
10
+ * - Custom action button with callback
11
+ */
12
+
13
+ import MapControl from '../../../shared/controls/MapControl.svelte';
14
+ import type { SiteStoreContext } from '../stores/siteStoreContext.svelte';
15
+
16
+ interface Props {
17
+ /** Site store context */
18
+ store: SiteStoreContext;
19
+ /** Control position */
20
+ position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
21
+ /** Control title */
22
+ title?: string;
23
+ /** Optional header icon */
24
+ icon?: string;
25
+ /** Show icon when collapsed (default: true) */
26
+ iconOnlyWhenCollapsed?: boolean;
27
+ /** Callback when action button clicked */
28
+ onAction?: (siteIds: string[]) => void;
29
+ /** Action button label */
30
+ actionButtonLabel?: string;
31
+ }
32
+
33
+ let {
34
+ store,
35
+ position = 'top-left',
36
+ title = 'Site Selection',
37
+ icon = 'check2-square',
38
+ iconOnlyWhenCollapsed = true,
39
+ onAction,
40
+ actionButtonLabel = 'Open'
41
+ }: Props = $props();
42
+
43
+ let selectedSites = $derived(store.getSelectedSites());
44
+ let selectionCount = $derived(selectedSites.length);
45
+ let hasSelection = $derived(selectionCount > 0);
46
+
47
+ function handleToggleMode() {
48
+ store.toggleSelectionMode();
49
+ }
50
+
51
+ function handleRemoveSite(siteId: string) {
52
+ store.removeSiteSelection(siteId);
53
+ }
54
+
55
+ function handleClearAll() {
56
+ store.clearSelection();
57
+ }
58
+
59
+ async function handleCopy() {
60
+ const ids = selectedSites.map(s => s.id).join(',');
61
+ try {
62
+ await navigator.clipboard.writeText(ids);
63
+ // Optional: Show toast notification
64
+ } catch (err) {
65
+ console.error('Failed to copy:', err);
66
+ }
67
+ }
68
+
69
+ function handleAction() {
70
+ if (onAction && hasSelection) {
71
+ const ids = selectedSites.map(s => s.id);
72
+ onAction(ids);
73
+ }
74
+ }
75
+ </script>
76
+
77
+ <MapControl {position} {title} {icon} {iconOnlyWhenCollapsed} collapsible={true}>
78
+ <div class="site-selection-control">
79
+ <!-- Selection Mode Toggle -->
80
+ <div class="selection-mode-toggle mb-3">
81
+ <div class="form-check form-switch">
82
+ <input
83
+ type="checkbox"
84
+ class="form-check-input"
85
+ id="selection-mode-toggle"
86
+ checked={store.selectionMode}
87
+ onchange={handleToggleMode}
88
+ />
89
+ <label class="form-check-label" for="selection-mode-toggle">
90
+ Selection Mode
91
+ <span class="badge ms-2" class:bg-success={store.selectionMode} class:bg-secondary={!store.selectionMode}>
92
+ {store.selectionMode ? 'ON' : 'OFF'}
93
+ </span>
94
+ </label>
95
+ </div>
96
+ {#if store.selectionMode}
97
+ <small class="text-muted d-block mt-1">
98
+ Click sites on the map to select
99
+ </small>
100
+ {/if}
101
+ </div>
102
+
103
+ <!-- Selection Stats -->
104
+ <div class="selection-stats mb-2">
105
+ <strong>{selectionCount}</strong>
106
+ {selectionCount === 1 ? 'site' : 'sites'} selected
107
+ </div>
108
+
109
+ <!-- Action Buttons -->
110
+ {#if hasSelection}
111
+ <div class="action-buttons mb-3">
112
+ <div class="btn-group w-100" role="group">
113
+ <button
114
+ type="button"
115
+ class="btn btn-sm btn-outline-danger"
116
+ onclick={handleClearAll}
117
+ title="Clear all"
118
+ >
119
+ <i class="bi bi-trash"></i> Clear
120
+ </button>
121
+ <button
122
+ type="button"
123
+ class="btn btn-sm btn-outline-secondary"
124
+ onclick={handleCopy}
125
+ title="Copy site IDs"
126
+ >
127
+ <i class="bi bi-clipboard"></i> Copy
128
+ </button>
129
+ </div>
130
+ </div>
131
+ {/if}
132
+
133
+ <!-- Site List -->
134
+ {#if hasSelection}
135
+ <div class="site-list">
136
+ {#each selectedSites as site (site.id)}
137
+ <div class="site-item">
138
+ <i class="bi bi-geo-alt-fill site-icon"></i>
139
+ <span class="site-id">{site.id}</span>
140
+ <button
141
+ type="button"
142
+ class="btn-remove"
143
+ onclick={() => handleRemoveSite(site.id)}
144
+ title="Remove"
145
+ aria-label="Remove {site.id}"
146
+ >
147
+ <i class="bi bi-x"></i>
148
+ </button>
149
+ </div>
150
+ {/each}
151
+ </div>
152
+ {:else}
153
+ <div class="text-muted small text-center py-2">
154
+ <i class="bi bi-inbox"></i>
155
+ <div class="mt-1">No sites selected</div>
156
+ </div>
157
+ {/if}
158
+
159
+ <!-- Action Button -->
160
+ {#if onAction}
161
+ <div class="mt-3">
162
+ <button
163
+ type="button"
164
+ class="btn btn-primary w-100"
165
+ disabled={!hasSelection}
166
+ onclick={handleAction}
167
+ >
168
+ <i class="bi bi-lightning-charge-fill"></i> {actionButtonLabel}
169
+ </button>
170
+ </div>
171
+ {/if}
172
+ </div>
173
+ </MapControl>
174
+
175
+ <style>
176
+ .site-selection-control {
177
+ width: 100%;
178
+ min-width: 250px;
179
+ }
180
+
181
+ .selection-mode-toggle {
182
+ padding-bottom: 0.75rem;
183
+ border-bottom: 1px solid #dee2e6;
184
+ }
185
+
186
+ .selection-stats {
187
+ font-size: 0.875rem;
188
+ color: #495057;
189
+ }
190
+
191
+ .action-buttons {
192
+ display: flex;
193
+ gap: 0.5rem;
194
+ }
195
+
196
+ .site-list {
197
+ max-height: 300px;
198
+ overflow-y: auto;
199
+ border: 1px solid #dee2e6;
200
+ border-radius: 4px;
201
+ padding: 0.5rem;
202
+ }
203
+
204
+ .site-item {
205
+ display: flex;
206
+ align-items: center;
207
+ gap: 0.5rem;
208
+ padding: 0.5rem;
209
+ border-bottom: 1px solid #f1f3f5;
210
+ transition: background-color 0.15s;
211
+ }
212
+
213
+ .site-item:last-child {
214
+ border-bottom: none;
215
+ }
216
+
217
+ .site-item:hover {
218
+ background-color: #f8f9fa;
219
+ }
220
+
221
+ .site-icon {
222
+ font-size: 1rem;
223
+ flex-shrink: 0;
224
+ color: #0d6efd;
225
+ }
226
+
227
+ .site-id {
228
+ flex: 1;
229
+ font-family: 'Monaco', 'Courier New', monospace;
230
+ font-size: 0.875rem;
231
+ color: #212529;
232
+ }
233
+
234
+ .btn-remove {
235
+ background: none;
236
+ border: none;
237
+ color: #6c757d;
238
+ font-size: 1.25rem;
239
+ line-height: 1;
240
+ padding: 0;
241
+ width: 24px;
242
+ height: 24px;
243
+ display: flex;
244
+ align-items: center;
245
+ justify-content: center;
246
+ cursor: pointer;
247
+ border-radius: 4px;
248
+ transition: all 0.15s;
249
+ }
250
+
251
+ .btn-remove:hover {
252
+ background-color: #ffe6e6;
253
+ color: #dc3545;
254
+ }
255
+
256
+ .btn-remove:active {
257
+ background-color: #ffcccc;
258
+ }
259
+
260
+ /* Ensure primary action button keeps Bootstrap styling inside Mapbox control */
261
+ .site-selection-control .btn-primary {
262
+ background-color: var(--bs-btn-bg, var(--bs-primary));
263
+ border-color: var(--bs-btn-border-color, var(--bs-primary));
264
+ color: var(--bs-btn-color, var(--bs-body-color));
265
+ }
266
+
267
+ .site-selection-control .btn-primary:hover,
268
+ .site-selection-control .btn-primary:focus {
269
+ background-color: var(--bs-btn-hover-bg, var(--bs-primary));
270
+ border-color: var(--bs-btn-hover-border-color, var(--bs-primary));
271
+ color: var(--bs-btn-hover-color, var(--bs-btn-color, var(--bs-body-color)));
272
+ }
273
+
274
+ .site-selection-control .btn-primary:disabled,
275
+ .site-selection-control .btn-primary:disabled:hover {
276
+ background-color: var(--bs-btn-disabled-bg, var(--bs-btn-bg, var(--bs-primary)));
277
+ border-color: var(--bs-btn-disabled-border-color, var(--bs-btn-border-color, var(--bs-primary)));
278
+ color: var(--bs-btn-disabled-color, var(--bs-btn-color, var(--bs-body-color)));
279
+ opacity: var(--bs-btn-disabled-opacity, 0.65);
280
+ }
281
+ </style>
@@ -0,0 +1,20 @@
1
+ import type { SiteStoreContext } from '../stores/siteStoreContext.svelte';
2
+ interface Props {
3
+ /** Site store context */
4
+ store: SiteStoreContext;
5
+ /** Control position */
6
+ position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
7
+ /** Control title */
8
+ title?: string;
9
+ /** Optional header icon */
10
+ icon?: string;
11
+ /** Show icon when collapsed (default: true) */
12
+ iconOnlyWhenCollapsed?: boolean;
13
+ /** Callback when action button clicked */
14
+ onAction?: (siteIds: string[]) => void;
15
+ /** Action button label */
16
+ actionButtonLabel?: string;
17
+ }
18
+ declare const SiteSelectionControl: import("svelte").Component<Props, {}, "">;
19
+ type SiteSelectionControl = ReturnType<typeof SiteSelectionControl>;
20
+ export default SiteSelectionControl;
@@ -17,6 +17,10 @@
17
17
  position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
18
18
  /** Control title */
19
19
  title?: string;
20
+ /** Optional header icon */
21
+ icon?: string;
22
+ /** Show icon when collapsed (default: true) */
23
+ iconOnlyWhenCollapsed?: boolean;
20
24
  /** Initially collapsed? */
21
25
  initiallyCollapsed?: boolean;
22
26
  /** Minimum size (default: 2) */
@@ -31,14 +35,16 @@
31
35
  store,
32
36
  position = 'top-right',
33
37
  title = 'Site Size',
34
- initiallyCollapsed = false,
38
+ icon = 'sliders',
39
+ iconOnlyWhenCollapsed = true,
40
+ initiallyCollapsed = true,
35
41
  min = 2,
36
42
  max = 20,
37
43
  step = 1
38
44
  }: Props = $props();
39
45
  </script>
40
46
 
41
- <MapControl {position} {title} collapsible={true} {initiallyCollapsed}>
47
+ <MapControl {position} {title} {icon} {iconOnlyWhenCollapsed} collapsible={true} {initiallyCollapsed}>
42
48
  <div class="site-size-controls">
43
49
  <!-- Size slider -->
44
50
  <div class="control-row">
@@ -6,6 +6,10 @@ interface Props {
6
6
  position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
7
7
  /** Control title */
8
8
  title?: string;
9
+ /** Optional header icon */
10
+ icon?: string;
11
+ /** Show icon when collapsed (default: true) */
12
+ iconOnlyWhenCollapsed?: boolean;
9
13
  /** Initially collapsed? */
10
14
  initiallyCollapsed?: boolean;
11
15
  /** Minimum size (default: 2) */
@@ -10,5 +10,6 @@ export { createSiteStoreContext } from './stores/siteStoreContext.svelte';
10
10
  export { default as SitesLayer } from './layers/SitesLayer.svelte';
11
11
  export { default as SiteFilterControl } from './controls/SiteFilterControl.svelte';
12
12
  export { default as SiteSizeSlider } from './controls/SiteSizeSlider.svelte';
13
+ export { default as SiteSelectionControl } from './controls/SiteSelectionControl.svelte';
13
14
  export { sitesToGeoJSON, siteToFeature } from './utils/siteGeoJSON';
14
15
  export { buildSiteTree, getFilteredSites } from './utils/siteTreeUtils';
@@ -11,6 +11,7 @@ export { default as SitesLayer } from './layers/SitesLayer.svelte';
11
11
  // Controls
12
12
  export { default as SiteFilterControl } from './controls/SiteFilterControl.svelte';
13
13
  export { default as SiteSizeSlider } from './controls/SiteSizeSlider.svelte';
14
+ export { default as SiteSelectionControl } from './controls/SiteSelectionControl.svelte';
14
15
  // Utils
15
16
  export { sitesToGeoJSON, siteToFeature } from './utils/siteGeoJSON';
16
17
  export { buildSiteTree, getFilteredSites } from './utils/siteTreeUtils';
@@ -188,6 +188,14 @@
188
188
  if (!e.features || e.features.length === 0) return;
189
189
  const feature = e.features[0];
190
190
  const siteProps = feature.properties;
191
+
192
+ // If selection mode is enabled, toggle selection instead of showing popup
193
+ if (store.selectionMode && siteProps?.id) {
194
+ store.toggleSiteSelection(siteProps.id);
195
+ return;
196
+ }
197
+
198
+ // Otherwise, show popup as normal
191
199
  if (siteProps && e.lngLat) {
192
200
  createSitePopup(siteProps, e.lngLat);
193
201
  }
@@ -240,7 +248,9 @@
240
248
 
241
249
  function handleMouseEnter(e: mapboxgl.MapLayerMouseEvent): void {
242
250
  if (!map) return;
243
- map.getCanvas().style.cursor = 'pointer';
251
+
252
+ // Change cursor based on mode
253
+ map.getCanvas().style.cursor = store.selectionMode ? 'crosshair' : 'pointer';
244
254
 
245
255
  if (!e.features || e.features.length === 0) return;
246
256
  const feature = e.features[0];
@@ -21,12 +21,19 @@ export declare function createSiteStoreContext(initialSites?: Site[]): {
21
21
  strokeWidth: number;
22
22
  strokeColor: string;
23
23
  groupColorMap: Map<string, string>;
24
+ selectionMode: boolean;
25
+ readonly selectedSiteIds: Set<string>;
24
26
  setAllSites(sites: Site[]): void;
25
27
  setFilteredSites(sites: Site[]): void;
26
28
  setSize(size: number): void;
27
29
  setColor(color: string): void;
28
30
  setOpacity(opacity: number): void;
29
31
  setShowLabels(show: boolean): void;
32
+ toggleSelectionMode(): void;
33
+ toggleSiteSelection(siteId: string): void;
34
+ removeSiteSelection(siteId: string): void;
35
+ clearSelection(): void;
36
+ getSelectedSites(): Site[];
30
37
  getGroupColor(groupKey: string): string | undefined;
31
38
  setGroupColor(groupKey: string, color: string): void;
32
39
  clearGroupColor(groupKey: string): void;
@@ -55,7 +55,9 @@ export function createSiteStoreContext(initialSites = []) {
55
55
  labelProperty: persistedSettings.labelProperty ?? 'name',
56
56
  groupColorMap: initialColorMap,
57
57
  strokeWidth: persistedSettings.strokeWidth ?? 2,
58
- strokeColor: persistedSettings.strokeColor ?? '#ffffff'
58
+ strokeColor: persistedSettings.strokeColor ?? '#ffffff',
59
+ selectionMode: false, // Not persisted - always starts disabled
60
+ selectedSiteIds: new Set() // Not persisted - always starts empty
59
61
  });
60
62
  // Auto-save settings when they change
61
63
  $effect(() => {
@@ -110,6 +112,10 @@ export function createSiteStoreContext(initialSites = []) {
110
112
  set strokeColor(value) { state.strokeColor = value; },
111
113
  get groupColorMap() { return state.groupColorMap; },
112
114
  set groupColorMap(value) { state.groupColorMap = value; },
115
+ // Selection properties
116
+ get selectionMode() { return state.selectionMode; },
117
+ set selectionMode(value) { state.selectionMode = value; },
118
+ get selectedSiteIds() { return state.selectedSiteIds; },
113
119
  // Convenience methods (optional, but nice to have)
114
120
  setAllSites(sites) { state.allSites = sites; },
115
121
  setFilteredSites(sites) { state.filteredSites = sites; },
@@ -117,6 +123,35 @@ export function createSiteStoreContext(initialSites = []) {
117
123
  setColor(color) { state.color = color; },
118
124
  setOpacity(opacity) { state.opacity = opacity; },
119
125
  setShowLabels(show) { state.showLabels = show; },
126
+ // Selection methods
127
+ toggleSelectionMode() {
128
+ state.selectionMode = !state.selectionMode;
129
+ // Clear selection when disabling selection mode
130
+ if (!state.selectionMode) {
131
+ state.selectedSiteIds = new Set();
132
+ }
133
+ },
134
+ toggleSiteSelection(siteId) {
135
+ const newSet = new Set(state.selectedSiteIds);
136
+ if (newSet.has(siteId)) {
137
+ newSet.delete(siteId);
138
+ }
139
+ else {
140
+ newSet.add(siteId);
141
+ }
142
+ state.selectedSiteIds = newSet;
143
+ },
144
+ removeSiteSelection(siteId) {
145
+ const newSet = new Set(state.selectedSiteIds);
146
+ newSet.delete(siteId);
147
+ state.selectedSiteIds = newSet;
148
+ },
149
+ clearSelection() {
150
+ state.selectedSiteIds = new Set();
151
+ },
152
+ getSelectedSites() {
153
+ return state.allSites.filter(site => state.selectedSiteIds.has(site.id));
154
+ },
120
155
  // Group color methods
121
156
  getGroupColor(groupKey) {
122
157
  return state.groupColorMap.get(groupKey);
@@ -146,6 +181,8 @@ export function createSiteStoreContext(initialSites = []) {
146
181
  state.strokeWidth = 2;
147
182
  state.strokeColor = '#ffffff';
148
183
  state.groupColorMap = new Map();
184
+ state.selectionMode = false;
185
+ state.selectedSiteIds = new Set();
149
186
  },
150
187
  // Get snapshot of current state (useful for debugging)
151
188
  getState() {
@@ -32,6 +32,8 @@ export interface SiteStoreValue {
32
32
  labelOffset: number;
33
33
  labelProperty: string;
34
34
  groupColorMap: Map<string, string>;
35
+ selectionMode: boolean;
36
+ selectedSiteIds: Set<string>;
35
37
  hoverColor?: string;
36
38
  selectedColor?: string;
37
39
  strokeWidth?: number;
@@ -6,6 +6,6 @@
6
6
  */
7
7
  export { type MapStore, MAP_CONTEXT_KEY, MapboxProvider, ViewportSync, MapStyleControl, createMapStore, createViewportStore, type ViewportStore, type ViewportState, useMapbox, tryUseMapbox } from './core';
8
8
  export { MapControl, addSourceIfMissing, removeSourceIfExists, addLayerIfMissing, removeLayerIfExists, updateGeoJSONSource, removeLayerAndSource, isStyleLoaded, waitForStyleLoad, setFeatureState, removeFeatureState, generateLayerId, generateSourceId } from './shared';
9
- export { type Site, type SiteStoreValue, type SiteStoreContext, createSiteStore, createSiteStoreContext, SitesLayer, SiteFilterControl, SiteSizeSlider, sitesToGeoJSON, siteToFeature, buildSiteTree, getFilteredSites } from './features/sites';
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 CellTreeConfig, type TechnologyBandKey, type CellStoreValue, type CellStoreContext, createCellStoreContext, CellsLayer, 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 { DemoMap, demoSites, demoCells } from './demo';
@@ -15,7 +15,7 @@ export { MapControl, addSourceIfMissing, removeSourceIfExists, addLayerIfMissing
15
15
  // ============================================================================
16
16
  // SITE FEATURE
17
17
  // ============================================================================
18
- export { createSiteStore, createSiteStoreContext, SitesLayer, SiteFilterControl, SiteSizeSlider, sitesToGeoJSON, siteToFeature, buildSiteTree, getFilteredSites } from './features/sites';
18
+ export { createSiteStore, createSiteStoreContext, SitesLayer, SiteFilterControl, SiteSelectionControl, SiteSizeSlider, sitesToGeoJSON, siteToFeature, buildSiteTree, getFilteredSites } from './features/sites';
19
19
  // ============================================================================
20
20
  // CELL FEATURE
21
21
  // ============================================================================
@@ -19,6 +19,10 @@
19
19
  position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
20
20
  /** Control title (shown in header) */
21
21
  title?: string;
22
+ /** Optional Bootstrap icon name to render when collapsed */
23
+ icon?: string;
24
+ /** When collapsed, should we show only the icon (if provided)? */
25
+ iconOnlyWhenCollapsed?: boolean;
22
26
  /** Is the control collapsible? */
23
27
  collapsible?: boolean;
24
28
  /** Initial collapsed state */
@@ -32,8 +36,10 @@
32
36
  let {
33
37
  position = 'top-left',
34
38
  title,
39
+ icon,
40
+ iconOnlyWhenCollapsed = true,
35
41
  collapsible = true,
36
- initiallyCollapsed = false,
42
+ initiallyCollapsed = true,
37
43
  className = '',
38
44
  children
39
45
  }: Props = $props();
@@ -107,9 +113,22 @@
107
113
  bind:this={controlElement}
108
114
  class="mapboxgl-ctrl mapboxgl-ctrl-group map-control-container {className}"
109
115
  >
110
- {#if title}
111
- <div class="map-control-header">
112
- <span class="map-control-title">{title}</span>
116
+ {#if title || icon}
117
+ <div class="map-control-header" title={title}>
118
+ <span class="map-control-title">
119
+ {#if collapsed && iconOnlyWhenCollapsed && icon}
120
+ <i class="bi bi-{icon}" aria-hidden="true"></i>
121
+ {#if title}
122
+ <span class="visually-hidden">{title}</span>
123
+ {/if}
124
+ {:else}
125
+ {#if title}
126
+ {title}
127
+ {:else if icon}
128
+ <i class="bi bi-{icon}" aria-hidden="true"></i>
129
+ {/if}
130
+ {/if}
131
+ </span>
113
132
  {#if collapsible}
114
133
  <button
115
134
  class="map-control-toggle"
@@ -159,6 +178,13 @@
159
178
  user-select: none;
160
179
  }
161
180
 
181
+ .map-control-title i {
182
+ display: inline-flex;
183
+ align-items: center;
184
+ justify-content: center;
185
+ font-size: 1.1rem;
186
+ }
187
+
162
188
  .map-control-toggle {
163
189
  background: none;
164
190
  border: none;
@@ -201,4 +227,15 @@
201
227
  .map-control-content::-webkit-scrollbar-thumb:hover {
202
228
  background: #555;
203
229
  }
230
+
231
+ .visually-hidden {
232
+ position: absolute;
233
+ width: 1px;
234
+ height: 1px;
235
+ padding: 0;
236
+ overflow: hidden;
237
+ clip: rect(0, 0, 0, 0);
238
+ white-space: nowrap;
239
+ border: 0;
240
+ }
204
241
  </style>
@@ -3,6 +3,10 @@ interface Props {
3
3
  position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
4
4
  /** Control title (shown in header) */
5
5
  title?: string;
6
+ /** Optional Bootstrap icon name to render when collapsed */
7
+ icon?: string;
8
+ /** When collapsed, should we show only the icon (if provided)? */
9
+ iconOnlyWhenCollapsed?: boolean;
6
10
  /** Is the control collapsible? */
7
11
  collapsible?: boolean;
8
12
  /** Initial collapsed state */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartnet360/svelte-components",
3
- "version": "0.0.65",
3
+ "version": "0.0.67",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run prepack",