@softwarity/geojson-editor 1.0.24 → 1.0.26

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.
@@ -11,10 +11,15 @@
11
11
  }
12
12
 
13
13
  :host {
14
+ /* Color scheme - inherits from parent, falls back to system preference if not set */
15
+ color-scheme: inherit;
16
+
17
+ /* Layout variables (internal only) */
14
18
  --line-height: 19.5px;
15
19
  --gutter-width: 50px;
16
20
  --editor-padding-y: 8px;
17
21
  --editor-padding-x: 12px;
22
+
18
23
  display: flex;
19
24
  flex-direction: column;
20
25
  position: relative;
@@ -39,7 +44,7 @@
39
44
  position: relative;
40
45
  width: 100%;
41
46
  flex: 1;
42
- background: var(--bg-color, #fff);
47
+ background: var(--geojson-editor-bg-color, light-dark(#fff, #2b2b2b));
43
48
  display: flex;
44
49
  overflow: hidden;
45
50
  }
@@ -47,8 +52,8 @@
47
52
  /* ========== Gutter ========== */
48
53
  .gutter {
49
54
  width: var(--gutter-width);
50
- background: var(--gutter-bg, #f0f0f0);
51
- border-right: 1px solid var(--gutter-border, #e0e0e0);
55
+ background: var(--geojson-editor-gutter-bg, light-dark(#f0f0f0, #313335));
56
+ border-right: 1px solid var(--geojson-editor-gutter-border, light-dark(#e0e0e0, #3c3f41));
52
57
  overflow: hidden;
53
58
  flex-shrink: 0;
54
59
  position: relative;
@@ -90,12 +95,12 @@
90
95
  top: 0;
91
96
  bottom: 0;
92
97
  width: 3px;
93
- background: var(--error-color, #dc3545);
98
+ background: var(--geojson-editor-error-color, light-dark(#dc3545, #ff6b68));
94
99
  }
95
100
 
96
101
  .line-number {
97
102
  font-size: 11px;
98
- color: var(--gutter-text, #999);
103
+ color: var(--geojson-editor-gutter-text, light-dark(#999, #606366));
99
104
  user-select: none;
100
105
  min-width: 20px;
101
106
  text-align: right;
@@ -118,7 +123,7 @@
118
123
  flex-shrink: 0;
119
124
  background: transparent;
120
125
  border: none;
121
- color: var(--json-punct, #a9b7c6);
126
+ color: var(--geojson-editor-json-punct, light-dark(#000, #a9b7c6));
122
127
  font-size: 10px;
123
128
  display: flex;
124
129
  align-items: center;
@@ -178,7 +183,7 @@
178
183
  padding: var(--editor-padding-y) var(--editor-padding-x);
179
184
  overscroll-behavior: contain; /* Prevent scroll chaining to parent */
180
185
  scrollbar-width: thin;
181
- scrollbar-color: var(--control-border, #c0c0c0) var(--control-bg, #e8e8e8);
186
+ scrollbar-color: var(--geojson-editor-control-border, light-dark(#c0c0c0, #5a5a5a)) var(--geojson-editor-control-bg, light-dark(#e8e8e8, #3c3f41));
182
187
  }
183
188
 
184
189
  /* Scroll content - defines total scrollable height */
@@ -209,7 +214,7 @@
209
214
  position: absolute;
210
215
  width: 2px;
211
216
  height: 1em;
212
- background: var(--caret-color, #000);
217
+ background: var(--geojson-editor-caret-color, light-dark(#000, #bbb));
213
218
  top: 0.15em;
214
219
  pointer-events: none;
215
220
  animation: cursor-blink 1s step-end infinite;
@@ -237,7 +242,7 @@
237
242
  position: absolute;
238
243
  height: 100%;
239
244
  top: 0;
240
- background: var(--selection-color, rgba(51, 153, 255, 0.3));
245
+ background: var(--geojson-editor-selection-color, light-dark(rgba(51, 153, 255, 0.3), rgba(51, 153, 255, 0.4)));
241
246
  pointer-events: none;
242
247
  z-index: 0;
243
248
  }
@@ -259,47 +264,47 @@
259
264
  }
260
265
 
261
266
  /* ========== Syntax Highlighting ========== */
262
- .json-key { color: var(--json-key, #660e7a); }
263
- .json-string { color: var(--json-string, #008000); }
264
- .json-number { color: var(--json-number, #00f); }
265
- .json-boolean, .json-null { color: var(--json-boolean, #000080); }
266
- .json-punctuation { color: var(--json-punct, #000); }
267
- .json-error { color: var(--json-error, #f00); }
267
+ .json-key { color: var(--geojson-editor-json-key, light-dark(#660e7a, #9876aa)); }
268
+ .json-string { color: var(--geojson-editor-json-string, light-dark(#008000, #6a8759)); }
269
+ .json-number { color: var(--geojson-editor-json-number, light-dark(#00f, #6897bb)); }
270
+ .json-boolean, .json-null { color: var(--geojson-editor-json-boolean, light-dark(#000080, #cc7832)); }
271
+ .json-punctuation { color: var(--geojson-editor-json-punct, light-dark(#000, #a9b7c6)); }
272
+ .json-error { color: var(--geojson-editor-json-error, light-dark(#f00, #ff6b68)); }
268
273
 
269
- .geojson-key { color: var(--geojson-key, #660e7a); font-weight: 600; }
270
- .geojson-type { color: var(--geojson-type, #008000); font-weight: 600; }
271
- .geojson-type-invalid { color: var(--geojson-type-invalid, #f00); font-weight: 600; }
274
+ .geojson-key { color: var(--geojson-editor-geojson-key, light-dark(#660e7a, #9876aa)); font-weight: 600; }
275
+ .geojson-type { color: var(--geojson-editor-geojson-type, light-dark(#008000, #6a8759)); font-weight: 600; }
276
+ .geojson-type-invalid { color: var(--geojson-editor-geojson-type-invalid, light-dark(#f00, #ff6b68)); font-weight: 600; }
272
277
 
273
278
  /* Collapsed node styling */
274
279
  .collapsed-bracket-array,
275
280
  .collapsed-bracket-object {
276
- color: var(--json-punct, #000);
281
+ color: var(--geojson-editor-json-punct, light-dark(#000, #a9b7c6));
277
282
  }
278
283
  .collapsed-bracket-array::after,
279
284
  .collapsed-bracket-object::after {
280
285
  content: '…';
281
- color: var(--json-punct, #888);
286
+ color: var(--geojson-editor-json-punct, light-dark(#000, #a9b7c6));
282
287
  }
283
288
 
284
289
  /* ========== Prefix/Suffix ========== */
285
290
  .prefix-wrapper, .suffix-wrapper {
286
291
  display: flex;
287
292
  flex-shrink: 0;
288
- background: var(--bg-color, #fff);
293
+ background: var(--geojson-editor-bg-color, light-dark(#fff, #2b2b2b));
289
294
  }
290
295
 
291
296
  .prefix-gutter, .suffix-gutter {
292
297
  width: var(--gutter-width);
293
- background: var(--gutter-bg, #f0f0f0);
294
- border-right: 1px solid var(--gutter-border, #e0e0e0);
298
+ background: var(--geojson-editor-gutter-bg, light-dark(#f0f0f0, #313335));
299
+ border-right: 1px solid var(--geojson-editor-gutter-border, light-dark(#e0e0e0, #3c3f41));
295
300
  flex-shrink: 0;
296
301
  }
297
302
 
298
303
  .editor-prefix, .editor-suffix {
299
304
  flex: 1;
300
305
  padding: 4px 12px;
301
- color: var(--text-color, #000);
302
- background: var(--bg-color, #fff);
306
+ color: var(--geojson-editor-text-color, light-dark(#000, #a9b7c6));
307
+ background: var(--geojson-editor-bg-color, light-dark(#fff, #2b2b2b));
303
308
  user-select: none;
304
309
  white-space: pre-wrap;
305
310
  word-wrap: break-word;
@@ -316,7 +321,7 @@
316
321
  transform: translateY(-50%);
317
322
  background: transparent;
318
323
  border: none;
319
- color: var(--text-color, #000);
324
+ color: var(--geojson-editor-text-color, light-dark(#000, #a9b7c6));
320
325
  opacity: 0.15;
321
326
  cursor: pointer;
322
327
  font-size: 0.7rem;
@@ -339,8 +344,8 @@
339
344
  top: 2rem;
340
345
  right: 0.5rem;
341
346
  z-index: 1000;
342
- background: var(--bg-color, #ffffff);
343
- border: 1px solid var(--gutter-border, #e0e0e0);
347
+ background: var(--geojson-editor-bg-color, light-dark(#fff, #2b2b2b));
348
+ border: 1px solid var(--geojson-editor-gutter-border, light-dark(#e0e0e0, #3c3f41));
344
349
  border-radius: 6px;
345
350
  box-shadow: 0 4px 12px rgba(0,0,0,0.15);
346
351
  padding: 12px 16px;
@@ -356,13 +361,13 @@
356
361
  .info-popup-title {
357
362
  font-weight: bold;
358
363
  font-size: 13px;
359
- color: var(--text-color, #000000);
364
+ color: var(--geojson-editor-text-color, light-dark(#000, #a9b7c6));
360
365
  margin-bottom: 4px;
361
366
  }
362
367
  .info-popup-version {
363
368
  display: block;
364
369
  font-size: 11px;
365
- color: var(--text-color, #000000);
370
+ color: var(--geojson-editor-text-color, light-dark(#000, #a9b7c6));
366
371
  opacity: 0.6;
367
372
  margin-bottom: 8px;
368
373
  text-decoration: none;
@@ -373,7 +378,7 @@
373
378
  }
374
379
  .info-popup-copyright {
375
380
  font-size: 10px;
376
- color: var(--text-color, #000000);
381
+ color: var(--geojson-editor-text-color, light-dark(#000, #a9b7c6));
377
382
  opacity: 0.5;
378
383
  }
379
384
 
@@ -384,7 +389,7 @@
384
389
  transform: translateY(-50%);
385
390
  background: transparent;
386
391
  border: none;
387
- color: var(--text-color, #000);
392
+ color: var(--geojson-editor-text-color, light-dark(#000, #a9b7c6));
388
393
  opacity: 0.3;
389
394
  cursor: pointer;
390
395
  font-size: 0.65rem;
@@ -413,7 +418,7 @@
413
418
  .error-nav-btn {
414
419
  background: transparent;
415
420
  border: none;
416
- color: var(--error-color, #dc3545);
421
+ color: var(--geojson-editor-error-color, light-dark(#dc3545, #ff6b68));
417
422
  cursor: pointer;
418
423
  font-size: 8px;
419
424
  width: 16px;
@@ -429,7 +434,7 @@
429
434
  opacity: 1;
430
435
  }
431
436
  .error-count {
432
- color: var(--error-color, #dc3545);
437
+ color: var(--geojson-editor-error-color, light-dark(#dc3545, #ff6b68));
433
438
  font-size: 11px;
434
439
  min-width: 20px;
435
440
  text-align: center;
@@ -437,9 +442,9 @@
437
442
 
438
443
  /* ========== Scrollbar ========== */
439
444
  .viewport::-webkit-scrollbar { width: 10px; height: 10px; }
440
- .viewport::-webkit-scrollbar-track { background: var(--control-bg, #e8e8e8); }
441
- .viewport::-webkit-scrollbar-thumb { background: var(--control-border, #c0c0c0); border-radius: 5px; }
442
- .viewport::-webkit-scrollbar-thumb:hover { background: var(--control-color, #000080); }
445
+ .viewport::-webkit-scrollbar-track { background: var(--geojson-editor-control-bg, light-dark(#e8e8e8, #3c3f41)); }
446
+ .viewport::-webkit-scrollbar-thumb { background: var(--geojson-editor-control-border, light-dark(#c0c0c0, #5a5a5a)); border-radius: 5px; }
447
+ .viewport::-webkit-scrollbar-thumb:hover { background: var(--geojson-editor-control-color, light-dark(#000080, #cc7832)); }
443
448
 
444
449
  /* ========== Inline Controls (using ::before pseudo-elements) ========== */
445
450
 
@@ -464,24 +469,24 @@
464
469
  .json-color:hover::before,
465
470
  .json-boolean:hover::before {
466
471
  transform: translateY(-50%) scale(1.2);
467
- border-color: var(--control-color, #000080);
472
+ border-color: var(--geojson-editor-control-color, light-dark(#000080, #cc7832));
468
473
  }
469
474
 
470
475
  /* Color swatch specific styles */
471
476
  .json-color::before {
472
477
  background-color: var(--swatch-color);
473
- border: 1px solid var(--json-punct, #a9b7c6);
478
+ border: 1px solid var(--geojson-editor-json-punct, light-dark(#000, #a9b7c6));
474
479
  }
475
480
 
476
481
  /* Boolean checkbox specific styles */
477
482
  .json-boolean::before {
478
- border: 1.5px solid var(--control-border, #c0c0c0);
483
+ border: 1.5px solid var(--geojson-editor-control-border, light-dark(#c0c0c0, #5a5a5a));
479
484
  background: transparent;
480
485
  }
481
486
  .json-bool-true::before {
482
487
  content: '✔';
483
- border-color: var(--control-color, #000080);
484
- color: var(--control-color, #000080);
488
+ border-color: var(--geojson-editor-control-color, light-dark(#000080, #cc7832));
489
+ color: var(--geojson-editor-control-color, light-dark(#000080, #cc7832));
485
490
  font-size: 8px;
486
491
  display: flex;
487
492
  align-items: center;
@@ -496,7 +501,7 @@
496
501
  left: 0;
497
502
  top: 3px;
498
503
  font-size: 10px;
499
- color: var(--control-color, #000080);
504
+ color: var(--geojson-editor-control-color, light-dark(#000080, #cc7832));
500
505
  opacity: 0.6;
501
506
  cursor: pointer;
502
507
  z-index: 1;
@@ -4,8 +4,7 @@ import type { Feature } from 'geojson';
4
4
 
5
5
  // ========== Imports from extracted modules ==========
6
6
  import type {
7
- SetOptions,
8
- ThemeSettings
7
+ SetOptions
9
8
  } from './types.js';
10
9
 
11
10
  import type {
@@ -35,19 +34,18 @@ import {
35
34
  RE_BRACKET_POS,
36
35
  RE_IS_WORD_CHAR,
37
36
  RE_ATTR_AND_BOOL_VALUE,
38
- RE_TO_KEBAB,
39
37
  RE_OPEN_BRACES,
40
38
  RE_CLOSE_BRACES,
41
39
  RE_OPEN_BRACKETS,
42
40
  RE_CLOSE_BRACKET
43
41
  } from './constants.js';
44
42
 
45
- import { createElement, countBrackets, parseSelectorToHostRule } from './utils.js';
43
+ import { createElement, countBrackets } from './utils.js';
46
44
  import { validateGeoJSON, normalizeToFeatures } from './validation.js';
47
45
  import { highlightSyntax, namedColorToHex, isNamedColor } from './syntax-highlighter.js';
48
46
 
49
47
  // Re-export public types
50
- export type { SetOptions, ThemeConfig, ThemeSettings } from './types.js';
48
+ export type { SetOptions } from './types.js';
51
49
 
52
50
  // Alias for minification
53
51
  const _ce = createElement;
@@ -94,8 +92,6 @@ class GeoJsonEditor extends HTMLElement {
94
92
  private renderTimer: ReturnType<typeof setTimeout> | undefined = undefined;
95
93
  private inputTimer: ReturnType<typeof setTimeout> | undefined = undefined;
96
94
 
97
- // ========== Theme ==========
98
- themes: ThemeSettings = { dark: {}, light: {} };
99
95
 
100
96
  // ========== Undo/Redo History ==========
101
97
  private _undoStack: EditorSnapshot[] = [];
@@ -116,6 +112,7 @@ class GeoJsonEditor extends HTMLElement {
116
112
  private _contextMapFirstLine: string | undefined = undefined;
117
113
  private _contextMapLastLine: string | undefined = undefined;
118
114
  private _errorLinesCache: Set<number> | null = null;
115
+ private _lastCurrentFeatureIndices: string | null = null; // For current-features event deduplication (JSON stringified indices)
119
116
 
120
117
  // ========== Cached DOM Elements ==========
121
118
  private _viewport: HTMLElement | null = null;
@@ -440,7 +437,7 @@ class GeoJsonEditor extends HTMLElement {
440
437
 
441
438
  // ========== Observed Attributes ==========
442
439
  static get observedAttributes() {
443
- return ['readonly', 'value', 'placeholder', 'dark-selector', 'internal-add-shortcut'];
440
+ return ['readonly', 'value', 'placeholder', 'internal-add-shortcut'];
444
441
  }
445
442
 
446
443
  // ========== Lifecycle ==========
@@ -449,7 +446,6 @@ class GeoJsonEditor extends HTMLElement {
449
446
  this._cacheElements();
450
447
  this.setupEventListeners();
451
448
  this.updatePrefixSuffix();
452
- this.updateThemeCSS();
453
449
 
454
450
  if (this.value) {
455
451
  this.setValue(this.value);
@@ -484,9 +480,6 @@ class GeoJsonEditor extends HTMLElement {
484
480
  case 'placeholder':
485
481
  this.updatePlaceholderContent();
486
482
  break;
487
- case 'dark-selector':
488
- this.updateThemeCSS();
489
- break;
490
483
  }
491
484
  }
492
485
 
@@ -697,12 +690,16 @@ class GeoJsonEditor extends HTMLElement {
697
690
  editorWrapper.classList.add('focused');
698
691
  this._invalidateRenderCache(); // Force re-render to show cursor
699
692
  this.scheduleRender();
693
+ // Emit current feature on focus (force to always emit on focus gain)
694
+ this._emitCurrentFeature(true);
700
695
  });
701
696
 
702
697
  hiddenTextarea.addEventListener('blur', () => {
703
698
  editorWrapper.classList.remove('focused');
704
699
  this._invalidateRenderCache(); // Force re-render to hide cursor
705
700
  this.scheduleRender();
701
+ // Emit null on blur
702
+ this._emitCurrentFeatureNull();
706
703
  });
707
704
 
708
705
  // Scroll handling
@@ -1324,11 +1321,16 @@ class GeoJsonEditor extends HTMLElement {
1324
1321
 
1325
1322
  linesContainer.innerHTML = '';
1326
1323
  linesContainer.appendChild(fragment);
1327
-
1324
+
1328
1325
  // Render gutter with same range
1329
1326
  this.renderGutter(startIndex, endIndex);
1327
+
1328
+ // Emit current-features event if feature changed (only when editor is focused)
1329
+ if (this._editorWrapper?.classList.contains('focused')) {
1330
+ this._emitCurrentFeature();
1331
+ }
1330
1332
  }
1331
-
1333
+
1332
1334
  /**
1333
1335
  * Insert cursor element at the specified column position
1334
1336
  * Uses absolute positioning to avoid affecting text layout
@@ -2237,7 +2239,7 @@ class GeoJsonEditor extends HTMLElement {
2237
2239
  }
2238
2240
 
2239
2241
  /**
2240
- * Scroll viewport to ensure cursor is visible
2242
+ * Scroll viewport to ensure cursor is visible with comfortable margin
2241
2243
  * @param center - if true, center the cursor line in the viewport
2242
2244
  */
2243
2245
  private _scrollToCursor(center = false) {
@@ -2250,6 +2252,8 @@ class GeoJsonEditor extends HTMLElement {
2250
2252
 
2251
2253
  const cursorY = visibleIndex * this.lineHeight;
2252
2254
  const viewportHeight = viewport.clientHeight;
2255
+ // Add margin of 2 lines for comfortable visibility
2256
+ const scrollMargin = this.lineHeight * 2;
2253
2257
 
2254
2258
  if (center) {
2255
2259
  // Center the cursor line in the viewport
@@ -2258,13 +2262,13 @@ class GeoJsonEditor extends HTMLElement {
2258
2262
  const viewportTop = viewport.scrollTop;
2259
2263
  const viewportBottom = viewportTop + viewportHeight;
2260
2264
 
2261
- // Scroll up if cursor is above viewport
2262
- if (cursorY < viewportTop) {
2263
- viewport.scrollTop = cursorY;
2265
+ // Scroll up if cursor is above viewport (with margin)
2266
+ if (cursorY < viewportTop + scrollMargin) {
2267
+ viewport.scrollTop = Math.max(0, cursorY - scrollMargin);
2264
2268
  }
2265
- // Scroll down if cursor is below viewport
2266
- else if (cursorY + this.lineHeight > viewportBottom) {
2267
- viewport.scrollTop = cursorY + this.lineHeight - viewportHeight;
2269
+ // Scroll down if cursor is below viewport (with margin)
2270
+ else if (cursorY + this.lineHeight > viewportBottom - scrollMargin) {
2271
+ viewport.scrollTop = cursorY + this.lineHeight + scrollMargin - viewportHeight;
2268
2272
  }
2269
2273
  }
2270
2274
  }
@@ -2757,7 +2761,14 @@ class GeoJsonEditor extends HTMLElement {
2757
2761
  // Try to parse as GeoJSON and normalize
2758
2762
  let pastedFeatureCount = 0;
2759
2763
  try {
2760
- const parsed = JSON.parse(text);
2764
+ // First try direct parse (single Feature, Feature[], or FeatureCollection)
2765
+ let parsed;
2766
+ try {
2767
+ parsed = JSON.parse(text);
2768
+ } catch {
2769
+ // If direct parse fails, try wrapping with [] (for "feature, feature" format from editor copy)
2770
+ parsed = JSON.parse('[' + text + ']');
2771
+ }
2761
2772
  const features = normalizeToFeatures(parsed);
2762
2773
  pastedFeatureCount = features.length;
2763
2774
  // Valid GeoJSON - insert formatted features
@@ -2808,6 +2819,12 @@ class GeoJsonEditor extends HTMLElement {
2808
2819
 
2809
2820
  // Force immediate render (not via RAF) to ensure content displays instantly
2810
2821
  this.renderViewport();
2822
+
2823
+ // Ensure cursor is visible in viewport after paste
2824
+ // Use RAF to ensure layout is updated before scrolling
2825
+ requestAnimationFrame(() => {
2826
+ this._scrollToCursor();
2827
+ });
2811
2828
  }
2812
2829
 
2813
2830
  handleCopy(e: ClipboardEvent): void {
@@ -3532,6 +3549,96 @@ class GeoJsonEditor extends HTMLElement {
3532
3549
  }
3533
3550
  }
3534
3551
 
3552
+ /**
3553
+ * Emit current-features event when cursor/selection changes
3554
+ * Includes all features that overlap with the selection (or just cursor position if no selection)
3555
+ * Only emits when the set of features changes (not on every cursor move)
3556
+ * @param force - If true, emit even if features haven't changed (used on focus)
3557
+ */
3558
+ private _emitCurrentFeature(force: boolean = false): void {
3559
+ // Collect feature indices based on cursor or selection
3560
+ const featureIndices = this._getFeatureIndicesForCurrentSelection();
3561
+
3562
+ // Stringify for comparison (deduplication)
3563
+ const indicesKey = JSON.stringify(featureIndices);
3564
+
3565
+ // Only emit if features changed (unless forced)
3566
+ if (!force && indicesKey === this._lastCurrentFeatureIndices) return;
3567
+ this._lastCurrentFeatureIndices = indicesKey;
3568
+
3569
+ if (featureIndices.length === 0) {
3570
+ // Cursor/selection is not in any feature - emit empty FeatureCollection
3571
+ this.dispatchEvent(new CustomEvent('current-features', {
3572
+ detail: { type: 'FeatureCollection', features: [] },
3573
+ bubbles: true,
3574
+ composed: true
3575
+ }));
3576
+ } else {
3577
+ // Get all features that overlap with cursor/selection
3578
+ const allFeatures = this._parseFeatures();
3579
+ const selectedFeatures = featureIndices
3580
+ .map(idx => allFeatures[idx])
3581
+ .filter(f => f != null);
3582
+
3583
+ const featureCollection = {
3584
+ type: 'FeatureCollection',
3585
+ features: selectedFeatures
3586
+ };
3587
+
3588
+ this.dispatchEvent(new CustomEvent('current-features', {
3589
+ detail: featureCollection,
3590
+ bubbles: true,
3591
+ composed: true
3592
+ }));
3593
+ }
3594
+ }
3595
+
3596
+ /**
3597
+ * Get all feature indices that overlap with the current cursor position or selection
3598
+ * Returns sorted unique indices
3599
+ */
3600
+ private _getFeatureIndicesForCurrentSelection(): number[] {
3601
+ const indices = new Set<number>();
3602
+
3603
+ // Determine the line range to check
3604
+ let startLine: number;
3605
+ let endLine: number;
3606
+
3607
+ if (this.selectionStart && this.selectionEnd) {
3608
+ // Selection exists - get all features overlapping the selection
3609
+ startLine = Math.min(this.selectionStart.line, this.selectionEnd.line);
3610
+ endLine = Math.max(this.selectionStart.line, this.selectionEnd.line);
3611
+ } else {
3612
+ // No selection - just use cursor position
3613
+ startLine = this.cursorLine;
3614
+ endLine = this.cursorLine;
3615
+ }
3616
+
3617
+ // Find all features that overlap with the line range
3618
+ for (const [, range] of this.featureRanges) {
3619
+ // Check if feature range overlaps with selection range
3620
+ if (range.startLine <= endLine && range.endLine >= startLine) {
3621
+ indices.add(range.featureIndex);
3622
+ }
3623
+ }
3624
+
3625
+ // Return sorted array for consistent comparison
3626
+ return Array.from(indices).sort((a, b) => a - b);
3627
+ }
3628
+
3629
+ /**
3630
+ * Emit current-features with empty FeatureCollection (used on blur)
3631
+ * Always emits to ensure map is cleared when editor loses focus
3632
+ */
3633
+ private _emitCurrentFeatureNull(): void {
3634
+ this._lastCurrentFeatureIndices = null;
3635
+ this.dispatchEvent(new CustomEvent('current-features', {
3636
+ detail: { type: 'FeatureCollection', features: [] },
3637
+ bubbles: true,
3638
+ composed: true
3639
+ }));
3640
+ }
3641
+
3535
3642
  // ========== UI Updates ==========
3536
3643
 
3537
3644
  updateReadonly() {
@@ -3573,74 +3680,6 @@ class GeoJsonEditor extends HTMLElement {
3573
3680
  if (this._editorSuffix) this._editorSuffix.textContent = this.suffix;
3574
3681
  }
3575
3682
 
3576
- // ========== Theme ==========
3577
-
3578
- updateThemeCSS() {
3579
- const darkSelector = this.getAttribute('dark-selector') || '.dark';
3580
- const darkRule = parseSelectorToHostRule(darkSelector);
3581
-
3582
- let themeStyle = this._id('theme-styles') as HTMLStyleElement;
3583
- if (!themeStyle) {
3584
- themeStyle = _ce('style') as HTMLStyleElement;
3585
- themeStyle.id = 'theme-styles';
3586
- this.shadowRoot!.insertBefore(themeStyle, this.shadowRoot!.firstChild);
3587
- }
3588
-
3589
- const darkDefaults = {
3590
- bgColor: '#2b2b2b',
3591
- textColor: '#a9b7c6',
3592
- caretColor: '#bbb',
3593
- gutterBg: '#313335',
3594
- gutterBorder: '#3c3f41',
3595
- gutterText: '#606366',
3596
- jsonKey: '#9876aa',
3597
- jsonString: '#6a8759',
3598
- jsonNumber: '#6897bb',
3599
- jsonBoolean: '#cc7832',
3600
- jsonNull: '#cc7832',
3601
- jsonPunct: '#a9b7c6',
3602
- jsonError: '#ff6b68',
3603
- controlColor: '#cc7832',
3604
- controlBg: '#3c3f41',
3605
- controlBorder: '#5a5a5a',
3606
- geojsonKey: '#9876aa',
3607
- geojsonType: '#6a8759',
3608
- geojsonTypeInvalid: '#ff6b68',
3609
- jsonKeyInvalid: '#ff6b68'
3610
- };
3611
-
3612
- RE_TO_KEBAB.lastIndex = 0;
3613
- const toKebab = (str: string) => str.replace(RE_TO_KEBAB, '-$1').toLowerCase();
3614
- const generateVars = (obj: Record<string, string | undefined>) => Object.entries(obj)
3615
- .filter((entry): entry is [string, string] => entry[1] !== undefined)
3616
- .map(([k, v]) => `--${toKebab(k)}: ${v};`)
3617
- .join('\n ');
3618
-
3619
- const lightVars = generateVars(this.themes.light as Record<string, string | undefined> || {});
3620
- const darkTheme = { ...darkDefaults, ...this.themes.dark };
3621
- const darkVars = generateVars(darkTheme as Record<string, string | undefined>);
3622
-
3623
- let css = lightVars ? `:host {\n ${lightVars}\n }\n` : '';
3624
- css += `${darkRule} {\n ${darkVars}\n }`;
3625
-
3626
- themeStyle.textContent = css;
3627
- }
3628
-
3629
- setTheme(theme: ThemeSettings): void {
3630
- if (theme.dark) this.themes.dark = { ...this.themes.dark, ...theme.dark };
3631
- if (theme.light) this.themes.light = { ...this.themes.light, ...theme.light };
3632
- this.updateThemeCSS();
3633
- }
3634
-
3635
- resetTheme(): void {
3636
- this.themes = { dark: {}, light: {} };
3637
- this.updateThemeCSS();
3638
- }
3639
-
3640
- getTheme(): ThemeSettings {
3641
- return { ...this.themes };
3642
- }
3643
-
3644
3683
  /**
3645
3684
  * Find all collapsible ranges using the mappings built by _rebuildNodeIdMappings
3646
3685
  * This method only READS the existing mappings, it doesn't create new IDs
@@ -4053,6 +4092,11 @@ class GeoJsonEditor extends HTMLElement {
4053
4092
  this.updateView();
4054
4093
  this.scheduleRender();
4055
4094
  this.emitChange();
4095
+ // Invalidate current-features cache and emit if focused (feature removal changes context)
4096
+ this._lastCurrentFeatureIndices = null;
4097
+ if (this._editorWrapper?.classList.contains('focused')) {
4098
+ this._emitCurrentFeature(true);
4099
+ }
4056
4100
  return removed;
4057
4101
  }
4058
4102
  return undefined;
@@ -4066,10 +4110,19 @@ class GeoJsonEditor extends HTMLElement {
4066
4110
  this.lines = [];
4067
4111
  this.collapsedNodes.clear();
4068
4112
  this.hiddenFeatures.clear();
4113
+ this.cursorLine = 0;
4114
+ this.cursorColumn = 0;
4115
+ this.selectionStart = null;
4116
+ this.selectionEnd = null;
4069
4117
  this.updateModel();
4070
4118
  this.scheduleRender();
4071
4119
  this.updatePlaceholderVisibility();
4072
4120
  this.emitChange();
4121
+ // Invalidate current-features cache and emit empty if focused (all features removed)
4122
+ this._lastCurrentFeatureIndices = null;
4123
+ if (this._editorWrapper?.classList.contains('focused')) {
4124
+ this._emitCurrentFeature(true);
4125
+ }
4073
4126
  return removed;
4074
4127
  }
4075
4128
 
package/src/types.ts CHANGED
@@ -16,33 +16,3 @@ export interface SetOptions {
16
16
  */
17
17
  collapsed?: string[] | ((feature: Feature | null, index: number) => string[]);
18
18
  }
19
-
20
- /** Theme configuration */
21
- export interface ThemeConfig {
22
- bgColor?: string;
23
- textColor?: string;
24
- caretColor?: string;
25
- gutterBg?: string;
26
- gutterBorder?: string;
27
- gutterText?: string;
28
- jsonKey?: string;
29
- jsonString?: string;
30
- jsonNumber?: string;
31
- jsonBoolean?: string;
32
- jsonNull?: string;
33
- jsonPunct?: string;
34
- jsonError?: string;
35
- controlColor?: string;
36
- controlBg?: string;
37
- controlBorder?: string;
38
- geojsonKey?: string;
39
- geojsonType?: string;
40
- geojsonTypeInvalid?: string;
41
- jsonKeyInvalid?: string;
42
- }
43
-
44
- /** Theme settings for dark and light modes */
45
- export interface ThemeSettings {
46
- dark?: ThemeConfig;
47
- light?: ThemeConfig;
48
- }
package/src/utils.ts CHANGED
@@ -26,13 +26,3 @@ export function countBrackets(line: string, openBracket: string): BracketCount {
26
26
  return { open, close };
27
27
  }
28
28
 
29
- /**
30
- * Parse a CSS selector to a :host rule for shadow DOM
31
- */
32
- export function parseSelectorToHostRule(selector: string | null): string {
33
- if (!selector) return ':host([data-color-scheme="dark"])';
34
- if (selector.startsWith('.') && !selector.includes(' ')) {
35
- return `:host(${selector})`;
36
- }
37
- return `:host-context(${selector})`;
38
- }