@smartnet360/svelte-components 0.0.87 → 0.0.88
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.
- package/dist/map-v3/core/components/MapStoreBridge.svelte +25 -0
- package/dist/map-v3/core/components/MapStoreBridge.svelte.d.ts +8 -0
- package/dist/map-v3/core/index.d.ts +1 -0
- package/dist/map-v3/core/index.js +1 -0
- package/dist/map-v3/demo/DemoMap.svelte +10 -0
- package/dist/map-v3/features/cells/components/CellFilterControl.svelte +24 -6
- package/dist/map-v3/features/cells/components/CellFilterControl.svelte.d.ts +3 -0
- package/dist/map-v3/features/cells/constants/statusStyles.d.ts +14 -0
- package/dist/map-v3/features/cells/constants/statusStyles.js +49 -0
- package/dist/map-v3/features/cells/constants.js +27 -13
- package/dist/map-v3/features/cells/layers/CellLabelsLayer.svelte +7 -2
- package/dist/map-v3/features/cells/layers/CellsLayer.svelte +16 -7
- package/dist/map-v3/features/cells/logic/geometry.js +22 -2
- package/dist/map-v3/features/repeaters/components/RepeaterFilterControl.svelte +14 -0
- package/dist/map-v3/features/repeaters/layers/RepeaterLabelsLayer.svelte +57 -26
- package/dist/map-v3/features/selection/components/FeatureSelectionControl.svelte +309 -0
- package/dist/map-v3/features/selection/components/FeatureSelectionControl.svelte.d.ts +20 -0
- package/dist/map-v3/features/selection/index.d.ts +9 -0
- package/dist/map-v3/features/selection/index.js +10 -0
- package/dist/map-v3/features/selection/layers/SelectionHighlightLayers.svelte +209 -0
- package/dist/map-v3/features/selection/layers/SelectionHighlightLayers.svelte.d.ts +13 -0
- package/dist/map-v3/features/selection/stores/selection.store.svelte.d.ts +84 -0
- package/dist/map-v3/features/selection/stores/selection.store.svelte.js +174 -0
- package/dist/map-v3/features/selection/types.d.ts +25 -0
- package/dist/map-v3/features/selection/types.js +4 -0
- package/dist/map-v3/features/sites/components/SiteFilterControl.svelte +14 -0
- package/dist/map-v3/features/sites/layers/SiteLabelsLayer.svelte +57 -26
- package/dist/map-v3/index.d.ts +1 -0
- package/dist/map-v3/index.js +2 -0
- package/dist/map-v3/shared/controls/MapControl.svelte +37 -10
- package/dist/map-v3/shared/controls/MapControl.svelte.d.ts +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feature Selection Store - Svelte 5 Runes Implementation
|
|
3
|
+
*
|
|
4
|
+
* Manages selection of map features (cells, sites) with click detection.
|
|
5
|
+
*/
|
|
6
|
+
export class FeatureSelectionStore {
|
|
7
|
+
selectedFeatures = $state([]);
|
|
8
|
+
map = $state(null);
|
|
9
|
+
selectionMode = $state(false);
|
|
10
|
+
idProperty = $state('siteId');
|
|
11
|
+
queryLayers = $state([
|
|
12
|
+
'cells-layer',
|
|
13
|
+
'sites-layer'
|
|
14
|
+
]);
|
|
15
|
+
clickHandler = null;
|
|
16
|
+
onSelectionChange;
|
|
17
|
+
/**
|
|
18
|
+
* Initialize the store with a map instance
|
|
19
|
+
*/
|
|
20
|
+
setMap(mapInstance) {
|
|
21
|
+
this.map = mapInstance;
|
|
22
|
+
this.setupClickHandler();
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Set which property to use as the ID
|
|
26
|
+
*/
|
|
27
|
+
setIdProperty(property) {
|
|
28
|
+
this.idProperty = property;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Set which layers to query for features
|
|
32
|
+
*/
|
|
33
|
+
setQueryLayers(layers) {
|
|
34
|
+
this.queryLayers = layers;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Register callback for selection changes
|
|
38
|
+
*/
|
|
39
|
+
setSelectionChangeCallback(callback) {
|
|
40
|
+
this.onSelectionChange = callback;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Setup global click handler for any feature
|
|
44
|
+
*/
|
|
45
|
+
setupClickHandler() {
|
|
46
|
+
if (!this.map)
|
|
47
|
+
return;
|
|
48
|
+
this.clickHandler = (e) => {
|
|
49
|
+
if (!this.selectionMode)
|
|
50
|
+
return;
|
|
51
|
+
// Query specific layers for features
|
|
52
|
+
const features = this.map.queryRenderedFeatures(e.point, {
|
|
53
|
+
layers: this.queryLayers
|
|
54
|
+
});
|
|
55
|
+
if (features && features.length > 0) {
|
|
56
|
+
// Get the topmost feature with an id
|
|
57
|
+
for (const feature of features) {
|
|
58
|
+
// Use the configured property as the ID
|
|
59
|
+
const featureId = feature.properties?.[this.idProperty] || feature.id;
|
|
60
|
+
const siteId = feature.properties?.siteId;
|
|
61
|
+
const cellName = feature.properties?.cellName;
|
|
62
|
+
if (featureId) {
|
|
63
|
+
this.toggleFeatureSelection(String(featureId), feature.layer?.id, feature.properties || undefined, siteId, cellName);
|
|
64
|
+
break; // Only select the topmost feature
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
this.map.on('click', this.clickHandler);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Enable selection mode
|
|
73
|
+
*/
|
|
74
|
+
enableSelectionMode() {
|
|
75
|
+
this.selectionMode = true;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Disable selection mode
|
|
79
|
+
*/
|
|
80
|
+
disableSelectionMode() {
|
|
81
|
+
this.selectionMode = false;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Toggle a feature in the selection
|
|
85
|
+
*/
|
|
86
|
+
toggleFeatureSelection(id, layerId, properties, siteId, cellName) {
|
|
87
|
+
const index = this.selectedFeatures.findIndex(f => f.id === id);
|
|
88
|
+
if (index >= 0) {
|
|
89
|
+
// Remove if already selected
|
|
90
|
+
this.selectedFeatures.splice(index, 1);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
// Add if not selected
|
|
94
|
+
this.selectedFeatures.push({ id, layerId, properties, siteId, cellName });
|
|
95
|
+
}
|
|
96
|
+
// Trigger callback
|
|
97
|
+
if (this.onSelectionChange) {
|
|
98
|
+
this.onSelectionChange(this.selectedFeatures);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Add a feature to the selection
|
|
103
|
+
*/
|
|
104
|
+
addFeatureSelection(id, layerId, properties, siteId, cellName) {
|
|
105
|
+
const exists = this.selectedFeatures.some(f => f.id === id);
|
|
106
|
+
if (!exists) {
|
|
107
|
+
this.selectedFeatures.push({ id, layerId, properties, siteId, cellName });
|
|
108
|
+
if (this.onSelectionChange) {
|
|
109
|
+
this.onSelectionChange(this.selectedFeatures);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Remove a feature from the selection
|
|
115
|
+
*/
|
|
116
|
+
removeFeatureSelection(id) {
|
|
117
|
+
const index = this.selectedFeatures.findIndex(f => f.id === id);
|
|
118
|
+
if (index >= 0) {
|
|
119
|
+
this.selectedFeatures.splice(index, 1);
|
|
120
|
+
if (this.onSelectionChange) {
|
|
121
|
+
this.onSelectionChange(this.selectedFeatures);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Clear all selections
|
|
127
|
+
*/
|
|
128
|
+
clearSelection() {
|
|
129
|
+
this.selectedFeatures = [];
|
|
130
|
+
if (this.onSelectionChange) {
|
|
131
|
+
this.onSelectionChange(this.selectedFeatures);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Get all selected features
|
|
136
|
+
*/
|
|
137
|
+
getSelectedFeatures() {
|
|
138
|
+
return this.selectedFeatures;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get selected feature IDs only
|
|
142
|
+
*/
|
|
143
|
+
getSelectedIds() {
|
|
144
|
+
return this.selectedFeatures.map(f => f.id);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Check if a feature is selected
|
|
148
|
+
*/
|
|
149
|
+
isFeatureSelected(id) {
|
|
150
|
+
return this.selectedFeatures.some(f => f.id === id);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Get selection count
|
|
154
|
+
*/
|
|
155
|
+
get count() {
|
|
156
|
+
return this.selectedFeatures.length;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Cleanup - remove event handlers
|
|
160
|
+
*/
|
|
161
|
+
destroy() {
|
|
162
|
+
if (this.map && this.clickHandler) {
|
|
163
|
+
this.map.off('click', this.clickHandler);
|
|
164
|
+
}
|
|
165
|
+
this.clearSelection();
|
|
166
|
+
this.map = null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Factory function to create a new feature selection store
|
|
171
|
+
*/
|
|
172
|
+
export function createFeatureSelectionStore() {
|
|
173
|
+
return new FeatureSelectionStore();
|
|
174
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feature Selection - Type Definitions
|
|
3
|
+
*/
|
|
4
|
+
export interface SelectedFeature {
|
|
5
|
+
/** Feature identifier */
|
|
6
|
+
id: string;
|
|
7
|
+
/** Optional site ID */
|
|
8
|
+
siteId?: string;
|
|
9
|
+
/** Optional cell name */
|
|
10
|
+
cellName?: string;
|
|
11
|
+
/** Layer ID where feature was selected from */
|
|
12
|
+
layerId?: string;
|
|
13
|
+
/** Feature properties */
|
|
14
|
+
properties?: Record<string, any>;
|
|
15
|
+
}
|
|
16
|
+
export interface SelectionStoreState {
|
|
17
|
+
/** Array of selected features */
|
|
18
|
+
selectedFeatures: SelectedFeature[];
|
|
19
|
+
/** Whether selection mode is active */
|
|
20
|
+
selectionMode: boolean;
|
|
21
|
+
/** Property name to use as feature ID */
|
|
22
|
+
idProperty: string;
|
|
23
|
+
/** Selection count */
|
|
24
|
+
count: number;
|
|
25
|
+
}
|
|
@@ -84,9 +84,23 @@
|
|
|
84
84
|
}
|
|
85
85
|
return '#3388ff';
|
|
86
86
|
}
|
|
87
|
+
|
|
88
|
+
function toggleLabels() {
|
|
89
|
+
displayStore.showLabels = !displayStore.showLabels;
|
|
90
|
+
}
|
|
87
91
|
</script>
|
|
88
92
|
|
|
89
93
|
<MapControl {position} title="Sites" icon="broadcast-pin" controlWidth="300px">
|
|
94
|
+
{#snippet actions()}
|
|
95
|
+
<button
|
|
96
|
+
class="btn btn-sm btn-outline-secondary border-0 p-1 px-2"
|
|
97
|
+
title={displayStore.showLabels ? 'Hide Labels' : 'Show Labels'}
|
|
98
|
+
aria-label={displayStore.showLabels ? 'Hide Labels' : 'Show Labels'}
|
|
99
|
+
onclick={toggleLabels}
|
|
100
|
+
>
|
|
101
|
+
<i class="bi bi-tag{displayStore.showLabels ? '-fill' : ''}"></i>
|
|
102
|
+
</button>
|
|
103
|
+
{/snippet}
|
|
90
104
|
<div class="p-2">
|
|
91
105
|
<!-- <div class="mb-2 text-muted small">
|
|
92
106
|
{dataStore.sites.length} Sites
|
|
@@ -118,35 +118,66 @@
|
|
|
118
118
|
features
|
|
119
119
|
};
|
|
120
120
|
|
|
121
|
+
// 4. Update Source
|
|
121
122
|
const source = map.getSource(sourceId) as mapboxgl.GeoJSONSource;
|
|
122
123
|
if (source) {
|
|
123
124
|
source.setData(geojson);
|
|
124
|
-
} else {
|
|
125
|
-
map.addSource(sourceId, {
|
|
126
|
-
type: 'geojson',
|
|
127
|
-
data: geojson
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
map.addLayer({
|
|
131
|
-
id: layerId,
|
|
132
|
-
type: 'symbol',
|
|
133
|
-
source: sourceId,
|
|
134
|
-
layout: {
|
|
135
|
-
'text-field': ['get', 'label'],
|
|
136
|
-
'text-font': ['Open Sans Semibold', 'Arial Unicode MS Bold'],
|
|
137
|
-
'text-size': displayStore.labelFontSize,
|
|
138
|
-
'text-offset': ['get', 'offset'],
|
|
139
|
-
'text-anchor': 'center',
|
|
140
|
-
'text-justify': 'auto',
|
|
141
|
-
'text-allow-overlap': false,
|
|
142
|
-
'text-ignore-placement': false
|
|
143
|
-
},
|
|
144
|
-
paint: {
|
|
145
|
-
'text-color': displayStore.labelColor,
|
|
146
|
-
'text-halo-color': displayStore.labelHaloColor,
|
|
147
|
-
'text-halo-width': displayStore.labelHaloWidth
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
125
|
}
|
|
151
126
|
}
|
|
127
|
+
|
|
128
|
+
// Initial Setup
|
|
129
|
+
$effect(() => {
|
|
130
|
+
const map = mapStore.map;
|
|
131
|
+
if (!map) return;
|
|
132
|
+
|
|
133
|
+
const addLayers = () => {
|
|
134
|
+
if (!map.getSource(sourceId)) {
|
|
135
|
+
map.addSource(sourceId, {
|
|
136
|
+
type: 'geojson',
|
|
137
|
+
data: { type: 'FeatureCollection', features: [] }
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (!map.getLayer(layerId)) {
|
|
142
|
+
map.addLayer({
|
|
143
|
+
id: layerId,
|
|
144
|
+
type: 'symbol',
|
|
145
|
+
source: sourceId,
|
|
146
|
+
layout: {
|
|
147
|
+
'text-field': ['get', 'label'],
|
|
148
|
+
'text-font': ['Open Sans Semibold', 'Arial Unicode MS Bold'],
|
|
149
|
+
'text-size': displayStore.labelFontSize,
|
|
150
|
+
'text-offset': ['get', 'offset'],
|
|
151
|
+
'text-anchor': 'center',
|
|
152
|
+
'text-justify': 'auto',
|
|
153
|
+
'text-allow-overlap': false,
|
|
154
|
+
'text-ignore-placement': false
|
|
155
|
+
},
|
|
156
|
+
paint: {
|
|
157
|
+
'text-color': displayStore.labelColor,
|
|
158
|
+
'text-halo-color': displayStore.labelHaloColor,
|
|
159
|
+
'text-halo-width': displayStore.labelHaloWidth
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const handleStyleLoad = () => {
|
|
166
|
+
addLayers();
|
|
167
|
+
updateLayer();
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
addLayers();
|
|
171
|
+
map.on('style.load', handleStyleLoad);
|
|
172
|
+
map.on('moveend', updateLayer);
|
|
173
|
+
map.on('zoomend', updateLayer);
|
|
174
|
+
|
|
175
|
+
return () => {
|
|
176
|
+
map.off('style.load', handleStyleLoad);
|
|
177
|
+
map.off('moveend', updateLayer);
|
|
178
|
+
map.off('zoomend', updateLayer);
|
|
179
|
+
if (map.getLayer(layerId)) map.removeLayer(layerId);
|
|
180
|
+
if (map.getSource(sourceId)) map.removeSource(sourceId);
|
|
181
|
+
};
|
|
182
|
+
});
|
|
152
183
|
</script>
|
package/dist/map-v3/index.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ export { default as SiteSettingsPanel } from './features/sites/components/SiteSe
|
|
|
24
24
|
export * from './features/sites/stores/site.data.svelte';
|
|
25
25
|
export * from './features/sites/stores/site.display.svelte';
|
|
26
26
|
export * from './features/sites/stores/site.registry.svelte';
|
|
27
|
+
export * from './features/selection';
|
|
27
28
|
export { default as DemoMap } from './demo/DemoMap.svelte';
|
|
28
29
|
export { demoCells } from './demo/demo-cells';
|
|
29
30
|
export { demoRepeaters } from './demo/demo-repeaters';
|
package/dist/map-v3/index.js
CHANGED
|
@@ -29,6 +29,8 @@ export { default as SiteSettingsPanel } from './features/sites/components/SiteSe
|
|
|
29
29
|
export * from './features/sites/stores/site.data.svelte';
|
|
30
30
|
export * from './features/sites/stores/site.display.svelte';
|
|
31
31
|
export * from './features/sites/stores/site.registry.svelte';
|
|
32
|
+
// Features - Selection
|
|
33
|
+
export * from './features/selection';
|
|
32
34
|
// Demo
|
|
33
35
|
export { default as DemoMap } from './demo/DemoMap.svelte';
|
|
34
36
|
export { demoCells } from './demo/demo-cells';
|
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
class?: string;
|
|
29
29
|
/** Child content */
|
|
30
30
|
children?: import('svelte').Snippet;
|
|
31
|
+
/** Optional action buttons in header */
|
|
32
|
+
actions?: import('svelte').Snippet;
|
|
31
33
|
/** Optional offset from map edge (e.g., '12px') */
|
|
32
34
|
edgeOffset?: string;
|
|
33
35
|
/** Width of the map control (e.g., '360px') */
|
|
@@ -44,6 +46,7 @@
|
|
|
44
46
|
onCollapseToggle,
|
|
45
47
|
class: className = '',
|
|
46
48
|
children,
|
|
49
|
+
actions,
|
|
47
50
|
edgeOffset = '12px',
|
|
48
51
|
controlWidth = '420px'
|
|
49
52
|
}: Props = $props();
|
|
@@ -127,16 +130,21 @@
|
|
|
127
130
|
{/if}
|
|
128
131
|
{/if}
|
|
129
132
|
</span>
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
133
|
+
<div class="map-control-actions">
|
|
134
|
+
{#if actions}
|
|
135
|
+
{@render actions()}
|
|
136
|
+
{/if}
|
|
137
|
+
{#if collapsible}
|
|
138
|
+
<button
|
|
139
|
+
class="map-control-toggle"
|
|
140
|
+
onclick={toggleCollapse}
|
|
141
|
+
aria-label={collapsed ? 'Expand' : 'Collapse'}
|
|
142
|
+
title={collapsed ? 'Expand' : 'Collapse'}
|
|
143
|
+
>
|
|
144
|
+
<i class="bi bi-chevron-{collapsed ? 'down' : 'up'}"></i>
|
|
145
|
+
</button>
|
|
146
|
+
{/if}
|
|
147
|
+
</div>
|
|
140
148
|
</div>
|
|
141
149
|
{/if}
|
|
142
150
|
|
|
@@ -186,6 +194,25 @@
|
|
|
186
194
|
font-size: 1.1rem;
|
|
187
195
|
}
|
|
188
196
|
|
|
197
|
+
.map-control-actions {
|
|
198
|
+
display: flex;
|
|
199
|
+
align-items: center;
|
|
200
|
+
gap: 4px;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.map-control-actions :global(.btn) {
|
|
204
|
+
display: flex;
|
|
205
|
+
align-items: center;
|
|
206
|
+
justify-content: center;
|
|
207
|
+
min-width: 28px;
|
|
208
|
+
height: 28px;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.map-control-actions :global(.btn i) {
|
|
212
|
+
font-size: 14px;
|
|
213
|
+
line-height: 1;
|
|
214
|
+
}
|
|
215
|
+
|
|
189
216
|
.map-control-toggle {
|
|
190
217
|
background: none;
|
|
191
218
|
border: none;
|
|
@@ -17,6 +17,8 @@ interface Props {
|
|
|
17
17
|
class?: string;
|
|
18
18
|
/** Child content */
|
|
19
19
|
children?: import('svelte').Snippet;
|
|
20
|
+
/** Optional action buttons in header */
|
|
21
|
+
actions?: import('svelte').Snippet;
|
|
20
22
|
/** Optional offset from map edge (e.g., '12px') */
|
|
21
23
|
edgeOffset?: string;
|
|
22
24
|
/** Width of the map control (e.g., '360px') */
|