@smartnet360/svelte-components 0.0.144 → 0.0.145

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.
@@ -110,11 +110,14 @@
110
110
  if (externalAntenna1) {
111
111
  const found = findAntennaBySpec(antennas, externalAntenna1);
112
112
  if (found) {
113
+ // Compute available tilts INLINE to avoid stale state issue
114
+ const tiltsForAntenna1 = getAvailableTiltsForAntenna(found);
115
+
113
116
  // Use untrack to prevent infinite loop from state updates
114
117
  untrack(() => {
115
118
  selectedAntenna = found;
116
- updateAvailableTilts(found);
117
- ant1ElectricalTilt = findTiltIndex(availableElectricalTilts, externalAntenna1.electricalTilt ?? 0);
119
+ availableElectricalTilts = tiltsForAntenna1;
120
+ ant1ElectricalTilt = findTiltIndex(tiltsForAntenna1, externalAntenna1.electricalTilt ?? 0);
118
121
  ant1MechanicalTilt = externalAntenna1.mechanicalTilt ?? 0;
119
122
  });
120
123
  } else {
@@ -126,9 +129,10 @@
126
129
  if (externalAntenna2) {
127
130
  const found2 = findAntennaBySpec(antennas, externalAntenna2);
128
131
  if (found2) {
132
+ const tiltsForAntenna2 = getAvailableTiltsForAntenna(found2);
129
133
  untrack(() => {
130
134
  selectedAntenna2 = found2;
131
- ant2ElectricalTilt = findTiltIndex(getAvailableTiltsForAntenna(found2), externalAntenna2.electricalTilt ?? 0);
135
+ ant2ElectricalTilt = findTiltIndex(tiltsForAntenna2, externalAntenna2.electricalTilt ?? 0);
132
136
  ant2MechanicalTilt = externalAntenna2.mechanicalTilt ?? 0;
133
137
  if (viewMode === 'single') {
134
138
  viewMode = 'compare';
@@ -71,6 +71,8 @@
71
71
  let csvHeaders = $state<string[]>([]);
72
72
  let columnMapping = $state<ColumnMapping>({});
73
73
  let importMode = $state<ImportMode>('cell');
74
+ let includeAllExtras = $state(true);
75
+ let selectedExtras = $state<number[]>([]);
74
76
 
75
77
  // Quick Add state
76
78
  let showQuickAdd = $state(false);
@@ -203,7 +205,11 @@
203
205
  if (!pendingCsvContent) return;
204
206
 
205
207
  try {
206
- importResult = setsStore.importFromCsv(pendingCsvContent, importFileName, columnMapping);
208
+ importResult = setsStore.importFromCsv(pendingCsvContent, importFileName, {
209
+ columnMapping,
210
+ includeAllExtras,
211
+ selectedExtraIndices: selectedExtras
212
+ });
207
213
  showMappingModal = false;
208
214
  showImportModal = true;
209
215
  } catch (err) {
@@ -219,6 +225,8 @@
219
225
  csvHeaders = [];
220
226
  columnMapping = {};
221
227
  importMode = 'cell';
228
+ includeAllExtras = true;
229
+ selectedExtras = [];
222
230
  }
223
231
 
224
232
  function confirmImport() {
@@ -596,8 +604,14 @@
596
604
  headers={csvHeaders}
597
605
  mode={importMode}
598
606
  mapping={columnMapping}
607
+ {includeAllExtras}
608
+ {selectedExtras}
599
609
  onmodechange={(m) => importMode = m}
600
610
  onchange={(m) => columnMapping = m}
611
+ onextraschange={(all, selected) => {
612
+ includeAllExtras = all;
613
+ selectedExtras = selected;
614
+ }}
601
615
  />
602
616
  </div>
603
617
  <div class="modal-footer py-2">
@@ -203,9 +203,7 @@
203
203
  customGroup: item.customGroup,
204
204
  sizeFactor: item.sizeFactor,
205
205
  setName: set.name,
206
- ...Object.fromEntries(
207
- Object.entries(item.extraFields).map(([k, v]) => [`extra_${k}`, v])
208
- )
206
+ popupInfo: { ...item.extraFields }
209
207
  }
210
208
  });
211
209
  } else if (item.geometry === 'cell' && item.resolvedCell) {
@@ -226,11 +224,7 @@
226
224
  feature.properties.customGroup = item.customGroup;
227
225
  feature.properties.sizeFactor = item.sizeFactor;
228
226
  feature.properties.setName = set.name;
229
-
230
- // Add extra fields
231
- for (const [key, value] of Object.entries(item.extraFields)) {
232
- feature.properties[`extra_${key}`] = value;
233
- }
227
+ feature.properties.popupInfo = { ...item.extraFields };
234
228
  }
235
229
 
236
230
  cellFeatures.push(feature);
@@ -26,15 +26,27 @@ export type CsvDelimiter = ',' | ';' | 'auto';
26
26
  * @returns Array of header strings
27
27
  */
28
28
  export declare function extractCsvHeaders(csvContent: string, delimiter?: CsvDelimiter): string[];
29
+ /**
30
+ * Options for CSV parsing
31
+ */
32
+ export interface CsvParseOptions {
33
+ /** Delimiter to use: ',' (comma), ';' (semicolon), or 'auto' (detect) */
34
+ delimiter?: CsvDelimiter;
35
+ /** User-defined column mapping (overrides auto-detection) */
36
+ columnMapping?: ColumnMapping;
37
+ /** Whether to include all extra columns (default: true) */
38
+ includeAllExtras?: boolean;
39
+ /** Specific extra column indices to include (when includeAllExtras is false) */
40
+ selectedExtraIndices?: number[];
41
+ }
29
42
  /**
30
43
  * Parse a CSV string into custom items (cells or points)
31
44
  * @param csvContent Raw CSV content
32
45
  * @param cellLookup Map of cellName -> Cell for resolving cell data
33
- * @param delimiter Delimiter to use: ',' (comma), ';' (semicolon), or 'auto' (detect)
34
- * @param columnMapping Optional user-defined column mapping (overrides auto-detection)
46
+ * @param options Parsing options
35
47
  * @returns Import result with items, unmatched IDs, groups, and extra columns
36
48
  */
37
- export declare function parseCustomCellsCsv(csvContent: string, cellLookup: Map<string, Cell>, delimiter?: CsvDelimiter, columnMapping?: ColumnMapping): CustomCellImportResult;
49
+ export declare function parseCustomCellsCsv(csvContent: string, cellLookup: Map<string, Cell>, options?: CsvParseOptions): CustomCellImportResult;
38
50
  /**
39
51
  * Build a cell lookup map from an array of cells
40
52
  * Creates lookups for both cellName and txId for flexible matching
@@ -49,11 +49,11 @@ export function extractCsvHeaders(csvContent, delimiter = 'auto') {
49
49
  * Parse a CSV string into custom items (cells or points)
50
50
  * @param csvContent Raw CSV content
51
51
  * @param cellLookup Map of cellName -> Cell for resolving cell data
52
- * @param delimiter Delimiter to use: ',' (comma), ';' (semicolon), or 'auto' (detect)
53
- * @param columnMapping Optional user-defined column mapping (overrides auto-detection)
52
+ * @param options Parsing options
54
53
  * @returns Import result with items, unmatched IDs, groups, and extra columns
55
54
  */
56
- export function parseCustomCellsCsv(csvContent, cellLookup, delimiter = 'auto', columnMapping) {
55
+ export function parseCustomCellsCsv(csvContent, cellLookup, options = {}) {
56
+ const { delimiter = 'auto', columnMapping, includeAllExtras = true, selectedExtraIndices = [] } = options;
57
57
  const lines = csvContent.trim().split('\n');
58
58
  if (lines.length < 2) {
59
59
  return {
@@ -106,16 +106,32 @@ export function parseCustomCellsCsv(csvContent, cellLookup, delimiter = 'auto',
106
106
  throw new Error('CSV must contain an "id", "cellName", or "txId" column');
107
107
  }
108
108
  const hasLatLon = latIndex !== -1 && lonIndex !== -1;
109
- // Find extra columns (not reserved)
109
+ // Find extra columns based on user selection
110
110
  const extraColumns = [];
111
111
  const extraIndices = [];
112
- headers.forEach((header, idx) => {
113
- const normalized = normalizeColumnName(header);
114
- if (!RESERVED_COLUMNS.includes(normalized)) {
115
- extraColumns.push(header.trim());
116
- extraIndices.push(idx);
112
+ // Get indices that are already mapped to known fields
113
+ const mappedIndices = new Set([idIndex, groupIndex, sizeFactorIndex, latIndex, lonIndex].filter(i => i >= 0));
114
+ if (includeAllExtras) {
115
+ // Include all non-reserved, non-mapped columns
116
+ headers.forEach((header, idx) => {
117
+ if (mappedIndices.has(idx))
118
+ return;
119
+ const normalized = normalizeColumnName(header);
120
+ if (!RESERVED_COLUMNS.includes(normalized)) {
121
+ extraColumns.push(header.trim());
122
+ extraIndices.push(idx);
123
+ }
124
+ });
125
+ }
126
+ else {
127
+ // Include only specifically selected columns
128
+ for (const idx of selectedExtraIndices) {
129
+ if (idx >= 0 && idx < headers.length && !mappedIndices.has(idx)) {
130
+ extraColumns.push(headers[idx].trim());
131
+ extraIndices.push(idx);
132
+ }
117
133
  }
118
- });
134
+ }
119
135
  // Parse data rows
120
136
  const cells = [];
121
137
  const unmatchedTxIds = [];
@@ -7,7 +7,7 @@
7
7
  import type { Cell } from '../../../../shared/demo';
8
8
  import type { CellDataStore } from '../../cells/stores/cell.data.svelte';
9
9
  import type { CustomCellSet, CustomCellImportResult } from '../types';
10
- import type { ColumnMapping } from '../../../../shared/csv-import';
10
+ import { type CsvParseOptions } from '../logic/csv-parser';
11
11
  /** Function that returns the current cells array */
12
12
  type CellsGetter = () => Cell[];
13
13
  /**
@@ -32,9 +32,9 @@ export declare class CustomCellSetsStore {
32
32
  * Import a CSV file and create a new custom cell set
33
33
  * @param csvContent Raw CSV content
34
34
  * @param fileName Name of the file being imported
35
- * @param columnMapping Optional user-defined column mapping
35
+ * @param options Parsing options (column mapping, extra fields selection)
36
36
  */
37
- importFromCsv(csvContent: string, fileName: string, columnMapping?: ColumnMapping): CustomCellImportResult;
37
+ importFromCsv(csvContent: string, fileName: string, options?: CsvParseOptions): CustomCellImportResult;
38
38
  /**
39
39
  * Create a new set from import result
40
40
  */
@@ -69,13 +69,13 @@ export class CustomCellSetsStore {
69
69
  * Import a CSV file and create a new custom cell set
70
70
  * @param csvContent Raw CSV content
71
71
  * @param fileName Name of the file being imported
72
- * @param columnMapping Optional user-defined column mapping
72
+ * @param options Parsing options (column mapping, extra fields selection)
73
73
  */
74
- importFromCsv(csvContent, fileName, columnMapping) {
74
+ importFromCsv(csvContent, fileName, options) {
75
75
  // Build lookup from all cells
76
76
  const cellLookup = buildCellLookup(this.getCells());
77
- // Parse CSV with optional user-defined column mapping
78
- const result = parseCustomCellsCsv(csvContent, cellLookup, 'auto', columnMapping);
77
+ // Parse CSV with options
78
+ const result = parseCustomCellsCsv(csvContent, cellLookup, options || {});
79
79
  return result;
80
80
  }
81
81
  /**
@@ -4,6 +4,7 @@
4
4
  *
5
5
  * Compact UI for mapping CSV columns to expected fields.
6
6
  * Includes import mode selection (Cell/Point/Site) with auto-detection.
7
+ * Supports extra field selection for tooltips.
7
8
  */
8
9
  import type { ColumnMapping, ImportFieldsConfig, ImportMode } from './types';
9
10
  import { getFieldsForMode } from './types';
@@ -16,18 +17,27 @@
16
17
  mode: ImportMode;
17
18
  /** Current mapping (can be auto-detected or user-modified) */
18
19
  mapping: ColumnMapping;
20
+ /** Whether to include all extra columns */
21
+ includeAllExtras?: boolean;
22
+ /** Selected extra column indices (when not including all) */
23
+ selectedExtras?: number[];
19
24
  /** Called when mode changes */
20
25
  onmodechange?: (mode: ImportMode) => void;
21
26
  /** Called when mapping changes */
22
27
  onchange?: (mapping: ColumnMapping) => void;
28
+ /** Called when extra fields settings change */
29
+ onextraschange?: (includeAll: boolean, selected: number[]) => void;
23
30
  }
24
31
 
25
32
  let {
26
33
  headers,
27
34
  mode,
28
35
  mapping,
36
+ includeAllExtras = true,
37
+ selectedExtras = [],
29
38
  onmodechange,
30
- onchange
39
+ onchange,
40
+ onextraschange
31
41
  }: Props = $props();
32
42
 
33
43
  // Get fields config based on current mode
@@ -36,6 +46,18 @@
36
46
  // All fields to display
37
47
  let allFields = $derived([...fieldsConfig.required, ...fieldsConfig.optional]);
38
48
 
49
+ // Get mapped column indices (to exclude from extra fields options)
50
+ let mappedIndices = $derived(new Set(
51
+ Object.values(mapping).filter((v): v is number => v !== null && v !== undefined)
52
+ ));
53
+
54
+ // Available columns for extra fields (not already mapped)
55
+ let availableExtras = $derived(
56
+ headers
57
+ .map((header, idx) => ({ header, idx }))
58
+ .filter(({ idx }) => !mappedIndices.has(idx))
59
+ );
60
+
39
61
  // Validation state
40
62
  let missingRequired = $derived(
41
63
  fieldsConfig.required.filter(f =>
@@ -78,6 +100,35 @@
78
100
  const idx = mapping[fieldType];
79
101
  return idx !== null && idx !== undefined ? headers[idx] : null;
80
102
  }
103
+
104
+ // Extra fields handlers
105
+ function handleIncludeAllChange(event: Event) {
106
+ const checked = (event.target as HTMLInputElement).checked;
107
+ onextraschange?.(checked, selectedExtras);
108
+ }
109
+
110
+ function handleExtraFieldChange(slotIndex: number, event: Event) {
111
+ const select = event.target as HTMLSelectElement;
112
+ const value = select.value;
113
+
114
+ const newSelected = [...selectedExtras];
115
+
116
+ if (value === '') {
117
+ // Remove this slot
118
+ newSelected[slotIndex] = -1;
119
+ } else {
120
+ newSelected[slotIndex] = parseInt(value, 10);
121
+ }
122
+
123
+ // Filter out -1 values and duplicates
124
+ const filtered = newSelected.filter((v, i) => v >= 0 && newSelected.indexOf(v) === i);
125
+ onextraschange?.(includeAllExtras, filtered);
126
+ }
127
+
128
+ function getExtraSlotValue(slotIndex: number): string {
129
+ const value = selectedExtras[slotIndex];
130
+ return value === undefined || value < 0 ? '' : String(value);
131
+ }
81
132
  </script>
82
133
 
83
134
  <div class="column-mapper">
@@ -150,13 +201,56 @@
150
201
  >
151
202
  <option value="">{field.description || '-- Select --'}</option>
152
203
  {#each headers as header, idx}
153
- <option value={idx}>{header}</option>
204
+ <option value={String(idx)}>{header}</option>
154
205
  {/each}
155
206
  </select>
156
207
  </div>
157
208
  {/each}
158
209
  </div>
159
210
 
211
+ <!-- Extra Fields Section -->
212
+ {#if availableExtras.length > 0}
213
+ <div class="extra-fields mt-3 pt-2 border-top">
214
+ <div class="d-flex align-items-center justify-content-between mb-2">
215
+ <span class="small fw-medium">Extra columns for popups</span>
216
+ <div class="form-check form-check-inline mb-0">
217
+ <input
218
+ class="form-check-input"
219
+ type="checkbox"
220
+ id="include-all-extras"
221
+ checked={includeAllExtras}
222
+ onchange={handleIncludeAllChange}
223
+ />
224
+ <label class="form-check-label small" for="include-all-extras">
225
+ Include all ({availableExtras.length})
226
+ </label>
227
+ </div>
228
+ </div>
229
+
230
+ {#if !includeAllExtras}
231
+ <div class="extra-slots">
232
+ {#each [0, 1, 2] as slotIndex}
233
+ {@const slotId = `extra-slot-${slotIndex}`}
234
+ <div class="d-flex align-items-center gap-2 mb-1">
235
+ <select
236
+ id={slotId}
237
+ class="form-select form-select-sm"
238
+ value={getExtraSlotValue(slotIndex)}
239
+ onchange={(e) => handleExtraFieldChange(slotIndex, e)}
240
+ aria-label={`Extra field ${slotIndex + 1}`}
241
+ >
242
+ <option value="">-- Extra {slotIndex + 1} --</option>
243
+ {#each availableExtras as { header, idx }}
244
+ <option value={String(idx)}>{header}</option>
245
+ {/each}
246
+ </select>
247
+ </div>
248
+ {/each}
249
+ </div>
250
+ {/if}
251
+ </div>
252
+ {/if}
253
+
160
254
  <!-- Validation Warning -->
161
255
  {#if missingRequired.length > 0}
162
256
  <div class="alert alert-warning py-1 px-2 mt-2 mb-0 small">
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * Compact UI for mapping CSV columns to expected fields.
5
5
  * Includes import mode selection (Cell/Point/Site) with auto-detection.
6
+ * Supports extra field selection for tooltips.
6
7
  */
7
8
  import type { ColumnMapping, ImportMode } from './types';
8
9
  interface Props {
@@ -12,10 +13,16 @@ interface Props {
12
13
  mode: ImportMode;
13
14
  /** Current mapping (can be auto-detected or user-modified) */
14
15
  mapping: ColumnMapping;
16
+ /** Whether to include all extra columns */
17
+ includeAllExtras?: boolean;
18
+ /** Selected extra column indices (when not including all) */
19
+ selectedExtras?: number[];
15
20
  /** Called when mode changes */
16
21
  onmodechange?: (mode: ImportMode) => void;
17
22
  /** Called when mapping changes */
18
23
  onchange?: (mapping: ColumnMapping) => void;
24
+ /** Called when extra fields settings change */
25
+ onextraschange?: (includeAll: boolean, selected: number[]) => void;
19
26
  }
20
27
  declare const ColumnMapper: import("svelte").Component<Props, {}, "">;
21
28
  type ColumnMapper = ReturnType<typeof ColumnMapper>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartnet360/svelte-components",
3
- "version": "0.0.144",
3
+ "version": "0.0.145",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run prepack",