maplibre-gl 6.2.0 → 6.4.0

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 (150) hide show
  1. package/dist/maplibre-gl-dev.mjs +1108 -427
  2. package/dist/maplibre-gl-dev.mjs.map +1 -1
  3. package/dist/maplibre-gl-shared-dev.mjs +359 -291
  4. package/dist/maplibre-gl-shared-dev.mjs.map +1 -1
  5. package/dist/maplibre-gl-shared.mjs +2 -2
  6. package/dist/maplibre-gl-shared.mjs.map +1 -1
  7. package/dist/maplibre-gl-worker-dev.mjs +23 -49
  8. package/dist/maplibre-gl-worker-dev.mjs.map +1 -1
  9. package/dist/maplibre-gl-worker.mjs +2 -2
  10. package/dist/maplibre-gl-worker.mjs.map +1 -1
  11. package/dist/maplibre-gl.css +1 -1
  12. package/dist/maplibre-gl.d.ts +467 -151
  13. package/dist/maplibre-gl.mjs +49 -49
  14. package/dist/maplibre-gl.mjs.map +1 -1
  15. package/package.json +12 -22
  16. package/src/data/bucket/fill_extrusion_bucket.ts +1 -13
  17. package/src/data/bucket/round_polygon_corners.test.ts +84 -36
  18. package/src/data/bucket/round_polygon_corners.ts +90 -39
  19. package/src/data/extent_bounds.ts +28 -1
  20. package/src/data/feature_index.bench.ts +21 -0
  21. package/src/data/segment.test.ts +2 -2
  22. package/src/geo/edge_insets.test.ts +1 -1
  23. package/src/geo/lng_lat.test.ts +7 -7
  24. package/src/geo/lng_lat_bounds.test.ts +4 -4
  25. package/src/geo/mercator_coordinate.test.ts +2 -2
  26. package/src/geo/projection/camera_helper.ts +5 -0
  27. package/src/geo/projection/globe_transform.test.ts +154 -1
  28. package/src/geo/projection/globe_transform.ts +7 -3
  29. package/src/geo/projection/globe_utils.ts +189 -2
  30. package/src/geo/projection/mercator_camera_helper.ts +1 -1
  31. package/src/geo/projection/mercator_transform.ts +21 -5
  32. package/src/geo/projection/vertical_perspective_camera_helper.test.ts +24 -0
  33. package/src/geo/projection/vertical_perspective_camera_helper.ts +71 -41
  34. package/src/geo/projection/vertical_perspective_transform.ts +7 -1
  35. package/src/geo/transform_interface.ts +13 -1
  36. package/src/index.ts +7 -2
  37. package/src/render/image_atlas.test.ts +180 -0
  38. package/src/render/image_atlas.ts +52 -6
  39. package/src/render/image_manager.test.ts +111 -0
  40. package/src/render/image_manager.ts +143 -144
  41. package/src/render/painter.ts +6 -14
  42. package/src/render/pattern_atlas.test.ts +131 -0
  43. package/src/render/pattern_atlas.ts +144 -0
  44. package/src/render/update_pattern_positions_in_program.test.ts +1 -1
  45. package/src/shaders/glsl/_prelude.vertex.glsl +10 -11
  46. package/src/shaders/glsl/_prelude.vertex.glsl.g.ts +1 -1
  47. package/src/shaders/glsl/circle.vertex.glsl +4 -4
  48. package/src/shaders/glsl/circle.vertex.glsl.g.ts +1 -1
  49. package/src/shaders/glsl/color_relief.fragment.glsl +1 -2
  50. package/src/shaders/glsl/color_relief.fragment.glsl.g.ts +1 -1
  51. package/src/shaders/glsl/fill_extrusion.vertex.glsl +3 -3
  52. package/src/shaders/glsl/fill_extrusion.vertex.glsl.g.ts +1 -1
  53. package/src/shaders/glsl/fill_extrusion_pattern.vertex.glsl +5 -5
  54. package/src/shaders/glsl/fill_extrusion_pattern.vertex.glsl.g.ts +1 -1
  55. package/src/shaders/glsl/heatmap.vertex.glsl +4 -4
  56. package/src/shaders/glsl/heatmap.vertex.glsl.g.ts +1 -1
  57. package/src/shaders/glsl/hillshade_prepare.fragment.glsl +12 -12
  58. package/src/shaders/glsl/hillshade_prepare.fragment.glsl.g.ts +1 -1
  59. package/src/shaders/glsl/line.vertex.glsl +7 -7
  60. package/src/shaders/glsl/line.vertex.glsl.g.ts +1 -1
  61. package/src/shaders/glsl/line_gradient.vertex.glsl +6 -6
  62. package/src/shaders/glsl/line_gradient.vertex.glsl.g.ts +1 -1
  63. package/src/shaders/glsl/line_gradient_sdf.vertex.glsl +7 -7
  64. package/src/shaders/glsl/line_gradient_sdf.vertex.glsl.g.ts +1 -1
  65. package/src/shaders/glsl/line_pattern.vertex.glsl +7 -7
  66. package/src/shaders/glsl/line_pattern.vertex.glsl.g.ts +1 -1
  67. package/src/shaders/glsl/line_sdf.vertex.glsl +7 -7
  68. package/src/shaders/glsl/line_sdf.vertex.glsl.g.ts +1 -1
  69. package/src/shaders/glsl/raster.fragment.glsl +4 -4
  70. package/src/shaders/glsl/raster.fragment.glsl.g.ts +1 -1
  71. package/src/shaders/glsl/raster.vertex.glsl +11 -6
  72. package/src/shaders/glsl/raster.vertex.glsl.g.ts +1 -1
  73. package/src/shaders/glsl/symbol_icon.vertex.glsl +5 -5
  74. package/src/shaders/glsl/symbol_icon.vertex.glsl.g.ts +1 -1
  75. package/src/shaders/glsl/symbol_sdf.vertex.glsl +5 -5
  76. package/src/shaders/glsl/symbol_sdf.vertex.glsl.g.ts +1 -1
  77. package/src/shaders/glsl/symbol_text_and_icon.vertex.glsl +6 -6
  78. package/src/shaders/glsl/symbol_text_and_icon.vertex.glsl.g.ts +1 -1
  79. package/src/source/canvas_source.test.ts +31 -0
  80. package/src/source/canvas_source.ts +2 -1
  81. package/src/source/geojson_worker_source.test.ts +88 -0
  82. package/src/source/geojson_worker_source.ts +22 -39
  83. package/src/source/image_source.test.ts +246 -38
  84. package/src/source/image_source.ts +132 -19
  85. package/src/source/raster_dem_tile_source.test.ts +37 -3
  86. package/src/source/raster_dem_tile_source.ts +1 -2
  87. package/src/source/raster_dem_tile_worker_source.test.ts +1 -1
  88. package/src/source/raster_tile_source.test.ts +5 -26
  89. package/src/source/raster_tile_source.ts +4 -3
  90. package/src/source/vector_tile_worker_source.test.ts +94 -2
  91. package/src/source/vector_tile_worker_source.ts +18 -27
  92. package/src/source/video_source.test.ts +12 -8
  93. package/src/source/worker_tile.bench.ts +10 -0
  94. package/src/source/worker_tile.ts +0 -3
  95. package/src/style/create_style_layer.bench.ts +15 -0
  96. package/src/style/properties.bench.ts +81 -0
  97. package/src/style/style.test.ts +75 -10
  98. package/src/style/style.ts +37 -64
  99. package/src/style/style_image.ts +67 -9
  100. package/src/style/style_layer.bench.ts +65 -0
  101. package/src/style/style_layer.test.ts +1 -1
  102. package/src/style/style_layer.ts +2 -2
  103. package/src/style/validate_style.bench.ts +11 -0
  104. package/src/style/validate_style.test.ts +2 -2
  105. package/src/symbol/anchor.test.ts +2 -2
  106. package/src/symbol/collision_index.bench.ts +116 -0
  107. package/src/symbol/cross_tile_symbol_index.bench.ts +49 -0
  108. package/src/symbol/cross_tile_symbol_index.test.ts +31 -0
  109. package/src/symbol/cross_tile_symbol_index.ts +67 -37
  110. package/src/symbol/shaping.test.ts +2 -1
  111. package/src/symbol/symbol_layout.bench.ts +30 -0
  112. package/src/tile/tile_id.test.ts +1 -1
  113. package/src/tile/tile_manager.test.ts +69 -1
  114. package/src/tile/tile_manager.ts +16 -11
  115. package/src/ui/control/fullscreen_control.test.ts +10 -10
  116. package/src/ui/control/geolocate_control.test.ts +3 -5
  117. package/src/ui/control/globe_control.test.ts +6 -6
  118. package/src/ui/events.ts +1 -1
  119. package/src/ui/handler/scroll_zoom.test.ts +50 -0
  120. package/src/ui/handler/two_fingers_touch.test.ts +17 -6
  121. package/src/ui/handler_inertia.test.ts +91 -0
  122. package/src/ui/handler_inertia.ts +65 -14
  123. package/src/ui/handler_manager.test.ts +243 -3
  124. package/src/ui/handler_manager.ts +79 -16
  125. package/src/ui/map.ts +17 -3
  126. package/src/ui/map_tests/map_cross_window.test.ts +2 -2
  127. package/src/ui/map_tests/map_events.test.ts +3 -3
  128. package/src/ui/map_tests/map_images.test.ts +55 -0
  129. package/src/ui/map_tests/map_style.test.ts +9 -8
  130. package/src/ui/map_tests/map_webgl.test.ts +30 -0
  131. package/src/ui/marker.test.ts +177 -42
  132. package/src/ui/marker.ts +120 -10
  133. package/src/ui/popup.test.ts +14 -14
  134. package/src/util/evented.test.ts +33 -28
  135. package/src/util/evented.ts +69 -43
  136. package/src/util/image_request.ts +31 -1
  137. package/src/util/test/null_gl.ts +10 -0
  138. package/src/util/test/util.ts +2 -1
  139. package/src/util/util.test.ts +1 -1
  140. package/src/util/web_worker_transfer.test.ts +5 -5
  141. package/src/webgl/context.ts +20 -0
  142. package/src/webgl/draw/draw_background.ts +1 -1
  143. package/src/webgl/draw/draw_raster.ts +11 -6
  144. package/src/webgl/program/pattern.ts +3 -3
  145. package/src/webgl/program/raster_program.ts +11 -0
  146. package/src/webgl/program.ts +31 -16
  147. package/src/webgl/texture.test.ts +16 -1
  148. package/src/webgl/texture.ts +3 -0
  149. package/src/webgl/vertex_buffer.test.ts +17 -2
  150. package/src/webgl/vertex_buffer.ts +24 -13
package/src/ui/marker.ts CHANGED
@@ -18,6 +18,19 @@ import type {PointLike} from './camera.ts';
18
18
  */
19
19
  export type Alignment = 'map' | 'viewport' | 'auto';
20
20
 
21
+ /**
22
+ * Screen-pixel deltas applied when a focused draggable marker is moved with the arrow keys.
23
+ */
24
+ const ARROW_KEY_DELTAS: Partial<Record<KeyboardEvent['key'], [number, number]>> = {
25
+ ArrowLeft: [-1, 0],
26
+ ArrowRight: [1, 0],
27
+ ArrowUp: [0, -1],
28
+ ArrowDown: [0, 1]
29
+ };
30
+
31
+ const KEYBOARD_DRAG_SMALL_STEP = 1;
32
+ const KEYBOARD_DRAG_LARGE_STEP = 10;
33
+
21
34
  /**
22
35
  * The {@link Marker} options object
23
36
  */
@@ -52,6 +65,10 @@ export type MarkerOptions = {
52
65
  scale?: number;
53
66
  /**
54
67
  * A boolean indicating whether or not a marker is able to be dragged to a new position on the map.
68
+ * A draggable default marker also becomes keyboard focusable and, while focused, moves by
69
+ * 1 screen pixel per arrow-key press (10 with Shift), firing the same `dragstart`/`drag`/`dragend`
70
+ * events as pointer dragging. Markers with a custom `element` keep their focusability and
71
+ * keyboard behavior application-owned.
55
72
  * @defaultValue false
56
73
  */
57
74
  draggable?: boolean;
@@ -222,12 +239,13 @@ export class Marker extends Evented<MarkerEventType> {
222
239
  _rotation: number;
223
240
  _pitchAlignment: Alignment;
224
241
  _rotationAlignment: Alignment;
225
- _originalTabIndex: string; // original tabindex of _element
226
242
  _opacity: string;
227
243
  _opacityWhenCovered: string;
228
244
  _opacityTimeout: ReturnType<typeof setTimeout>;
229
245
  _subpixelPositioning: boolean;
230
246
  _roleManaged: boolean;
247
+ _tabIndexManaged: boolean;
248
+ _keyboardDragActive: boolean;
231
249
 
232
250
  /**
233
251
  * @param options - the options
@@ -243,6 +261,8 @@ export class Marker extends Evented<MarkerEventType> {
243
261
  this._subpixelPositioning = options?.subpixelPositioning || false;
244
262
  this._isDragging = false;
245
263
  this._roleManaged = false;
264
+ this._tabIndexManaged = false;
265
+ this._keyboardDragActive = false;
246
266
  this._state = 'inactive';
247
267
  this._rotation = options?.rotation || 0;
248
268
  this._rotationAlignment = options?.rotationAlignment || 'auto';
@@ -447,6 +467,12 @@ export class Marker extends Evented<MarkerEventType> {
447
467
  delete this._map;
448
468
  }
449
469
  this._element.removeEventListener('click', this._onClick);
470
+ this._element.removeEventListener('keydown', this._onKeyDown);
471
+ this._element.removeEventListener('keyup', this._onKeyUp);
472
+ this._element.removeEventListener('blur', this._onBlur);
473
+ this._element.removeEventListener('keypress', this._onKeyPress);
474
+ // Drop any in-flight keyboard drag silently, like a pointer drag interrupted by remove().
475
+ this._keyboardDragActive = false;
450
476
  this._element.remove();
451
477
  if (this._popup) this._popup.remove();
452
478
  return this;
@@ -520,10 +546,6 @@ export class Marker extends Evented<MarkerEventType> {
520
546
  this._popup.remove();
521
547
  this._popup = null;
522
548
  this._element.removeEventListener('keypress', this._onKeyPress);
523
-
524
- if (!this._originalTabIndex) {
525
- this._element.removeAttribute('tabindex');
526
- }
527
549
  }
528
550
 
529
551
  if (popup) {
@@ -544,13 +566,10 @@ export class Marker extends Evented<MarkerEventType> {
544
566
  }
545
567
  this._popup = popup;
546
568
 
547
- this._originalTabIndex = this._element.getAttribute('tabindex');
548
- if (!this._originalTabIndex) {
549
- this._element.setAttribute('tabindex', '0');
550
- }
551
569
  this._element.addEventListener('keypress', this._onKeyPress);
552
570
  }
553
571
 
572
+ this._updateTabIndex();
554
573
  this._updateAccessibilityRole();
555
574
  return this;
556
575
  }
@@ -581,6 +600,57 @@ export class Marker extends Evented<MarkerEventType> {
581
600
  }
582
601
  };
583
602
 
603
+ /**
604
+ * Move a focused draggable default marker with the arrow keys
605
+ * (1 screen pixel per keydown; 10 with Shift).
606
+ * Mirrors the pointer drag gesture: the position updates before `dragstart`
607
+ * fires on the first movement, every movement fires `drag`, and releasing
608
+ * the arrow key (or losing focus) fires `dragend`. Holding a key down
609
+ * produces repeated `drag` events within a single gesture.
610
+ */
611
+ _onKeyDown = (e: KeyboardEvent): void => {
612
+ // Custom marker elements own their keyboard behavior (#7790).
613
+ if (!this._defaultMarker || !this._draggable || !this._map || !this._lngLat) return;
614
+ // Leave keys originating from elements nested inside the marker to the application.
615
+ if (e.composedPath()[0] !== this._element) return;
616
+ // Leave Alt/Ctrl/Meta shortcuts to the browser and the application.
617
+ if (e.altKey || e.ctrlKey || e.metaKey) return;
618
+
619
+ const delta = ARROW_KEY_DELTAS[e.key];
620
+ if (!delta) return;
621
+
622
+ e.preventDefault();
623
+ // The marker element lives in the canvas container, so without this the
624
+ // map's KeyboardHandler would also pan (or rotate/pitch with Shift) the camera.
625
+ e.stopPropagation();
626
+
627
+ const step = e.shiftKey ? KEYBOARD_DRAG_LARGE_STEP : KEYBOARD_DRAG_SMALL_STEP;
628
+ const pos = this._map.project(this._lngLat);
629
+ this.setLngLat(this._map.unproject(new Point(pos.x + delta[0] * step, pos.y + delta[1] * step)));
630
+
631
+ if (!this._keyboardDragActive) {
632
+ this._keyboardDragActive = true;
633
+ this.fire(new MarkerDragEvent('dragstart'));
634
+ }
635
+ this.fire(new MarkerDragEvent('drag'));
636
+ };
637
+
638
+ _onKeyUp = (e: KeyboardEvent): void => {
639
+ if (!ARROW_KEY_DELTAS[e.key]) return;
640
+ this._endKeyboardDrag();
641
+ };
642
+
643
+ _onBlur = (): void => {
644
+ this._endKeyboardDrag();
645
+ };
646
+
647
+ _endKeyboardDrag(): void {
648
+ if (this._keyboardDragActive) {
649
+ this._keyboardDragActive = false;
650
+ this.fire(new MarkerDragEvent('dragend'));
651
+ }
652
+ }
653
+
584
654
  _onMapClick = (e: MapMouseEvent): void => {
585
655
  const targetElement = e.originalEvent.target;
586
656
  const element = this._element;
@@ -851,7 +921,10 @@ export class Marker extends Evented<MarkerEventType> {
851
921
  };
852
922
 
853
923
  /**
854
- * Sets the `draggable` property and functionality of the marker
924
+ * Sets the `draggable` property and functionality of the marker.
925
+ * A draggable default marker is also keyboard focusable and movable with the
926
+ * arrow keys (see {@link MarkerOptions.draggable}); custom marker elements keep
927
+ * their focusability and keyboard behavior application-owned.
855
928
  * @param shouldBeDraggable - Turns drag functionality on/off
856
929
  */
857
930
  setDraggable(shouldBeDraggable?: boolean): this {
@@ -870,6 +943,20 @@ export class Marker extends Evented<MarkerEventType> {
870
943
  }
871
944
  }
872
945
 
946
+ if (this._defaultMarker) {
947
+ if (this._draggable) {
948
+ this._element.addEventListener('keydown', this._onKeyDown);
949
+ this._element.addEventListener('keyup', this._onKeyUp);
950
+ this._element.addEventListener('blur', this._onBlur);
951
+ } else {
952
+ this._element.removeEventListener('keydown', this._onKeyDown);
953
+ this._element.removeEventListener('keyup', this._onKeyUp);
954
+ this._element.removeEventListener('blur', this._onBlur);
955
+ this._endKeyboardDrag();
956
+ }
957
+ }
958
+
959
+ this._updateTabIndex();
873
960
  this._updateAccessibilityRole();
874
961
  return this;
875
962
  }
@@ -882,6 +969,29 @@ export class Marker extends Evented<MarkerEventType> {
882
969
  return this._draggable;
883
970
  }
884
971
 
972
+ /**
973
+ * Keep the marker element focusable while it has built-in keyboard behavior.
974
+ * A popup makes any marker element interactive; dragging only manages
975
+ * focusability for the default marker so custom elements stay
976
+ * application-owned (#7790). A tabindex supplied by the application is
977
+ * never added, changed, or removed here.
978
+ */
979
+ _updateTabIndex(): void {
980
+ const needsTabIndex = !!this._popup || (this._defaultMarker && this._draggable);
981
+ if (needsTabIndex) {
982
+ if (!this._element.hasAttribute('tabindex')) {
983
+ this._element.setAttribute('tabindex', '0');
984
+ this._tabIndexManaged = true;
985
+ }
986
+ } else if (this._tabIndexManaged) {
987
+ // Only remove the value we set; the application may have overridden it since.
988
+ if (this._element.getAttribute('tabindex') === '0') {
989
+ this._element.removeAttribute('tabindex');
990
+ }
991
+ this._tabIndexManaged = false;
992
+ }
993
+ }
994
+
885
995
  /**
886
996
  * Keep the default marker role aligned with interactivity.
887
997
  * Default markers need a role because `aria-label` is set in {@link Marker.addTo}.
@@ -32,7 +32,7 @@ describe('popup', () => {
32
32
  .addTo(map);
33
33
 
34
34
  expect(popup.isOpen()).toBeTruthy();
35
- expect(popup.getElement().classList.contains('maplibregl-popup')).toBeTruthy();
35
+ expect(popup.getElement().classList).toContain('maplibregl-popup');
36
36
  });
37
37
 
38
38
  test('Popup.addTo adds a .maplibregl-popup element', () => {
@@ -258,10 +258,10 @@ describe('popup', () => {
258
258
  test('Popup provides LngLat accessors', () => {
259
259
  expect(new Popup().getLngLat()).toBeUndefined();
260
260
 
261
- expect(new Popup().setLngLat([1, 2]).getLngLat() instanceof LngLat).toBeTruthy();
261
+ expect(new Popup().setLngLat([1, 2]).getLngLat()).toBeInstanceOf(LngLat);
262
262
  expect(new Popup().setLngLat([1, 2]).getLngLat()).toEqual(new LngLat(1, 2));
263
263
 
264
- expect(new Popup().setLngLat(new LngLat(1, 2)).getLngLat() instanceof LngLat).toBeTruthy();
264
+ expect(new Popup().setLngLat(new LngLat(1, 2)).getLngLat()).toBeInstanceOf(LngLat);
265
265
  expect(new Popup().setLngLat(new LngLat(1, 2)).getLngLat()).toEqual(new LngLat(1, 2));
266
266
 
267
267
  });
@@ -382,7 +382,7 @@ describe('popup', () => {
382
382
  .setText('Test')
383
383
  .addTo(map);
384
384
 
385
- expect(popup.getElement().classList.contains('maplibregl-popup-anchor-top-left')).toBeTruthy();
385
+ expect(popup.getElement().classList).toContain('maplibregl-popup-anchor-top-left');
386
386
  });
387
387
 
388
388
  const cases = [
@@ -413,7 +413,7 @@ describe('popup', () => {
413
413
  vi.spyOn(map, 'project').mockReturnValue(point);
414
414
  popup.setLngLat([0, 0]);
415
415
 
416
- expect(popup.getElement().classList.contains(`maplibregl-popup-anchor-${anchor}`)).toBeTruthy();
416
+ expect(popup.getElement().classList).toContain(`maplibregl-popup-anchor-${anchor}`);
417
417
  });
418
418
 
419
419
  const transformCases = cases.map(([anchor, _point, transform]) => [anchor, transform] as const);
@@ -449,7 +449,7 @@ describe('popup', () => {
449
449
  vi.spyOn(map, 'project').mockReturnValue(point);
450
450
  popup.setLngLat([0, 0]);
451
451
 
452
- expect(popup.getElement().classList.contains('maplibregl-popup-anchor-top')).toBeTruthy();
452
+ expect(popup.getElement().classList).toContain('maplibregl-popup-anchor-top');
453
453
  });
454
454
 
455
455
  test('Popup is offset via a PointLike offset option', () => {
@@ -563,22 +563,22 @@ describe('popup', () => {
563
563
  .addTo(map);
564
564
 
565
565
  const popupContainer = popup.getElement();
566
- expect(popupContainer.classList.contains('some')).toBeTruthy();
567
- expect(popupContainer.classList.contains('classes')).toBeTruthy();
566
+ expect(popupContainer.classList).toContain('some');
567
+ expect(popupContainer.classList).toContain('classes');
568
568
 
569
569
  const addClassNameMethodPopupInstance = popup.addClassName('addedClass');
570
- expect(popupContainer.classList.contains('addedClass')).toBeTruthy();
570
+ expect(popupContainer.classList).toContain('addedClass');
571
571
  expect(addClassNameMethodPopupInstance).toBeInstanceOf(Popup);
572
572
 
573
573
  const removeClassNameMethodPopupInstance = popup.removeClassName('addedClass');
574
- expect(!popupContainer.classList.contains('addedClass')).toBeTruthy();
574
+ expect(popupContainer.classList).not.toContain('addedClass');
575
575
  expect(removeClassNameMethodPopupInstance).toBeInstanceOf(Popup);
576
576
 
577
577
  popup.toggleClassName('toggle');
578
- expect(popupContainer.classList.contains('toggle')).toBeTruthy();
578
+ expect(popupContainer.classList).toContain('toggle');
579
579
 
580
580
  popup.toggleClassName('toggle');
581
- expect(!popupContainer.classList.contains('toggle')).toBeTruthy();
581
+ expect(popupContainer.classList).not.toContain('toggle');
582
582
 
583
583
  expect(() => popup.addClassName('should throw exception')).toThrow(window.DOMException);
584
584
  expect(() => popup.removeClassName('should throw exception')).toThrow(window.DOMException);
@@ -1063,8 +1063,8 @@ describe('popup', () => {
1063
1063
  .trackPointer()
1064
1064
  .addTo(map);
1065
1065
 
1066
- expect(popup.getElement().classList.contains('maplibregl-popup-track-pointer')).toBeTruthy();
1067
- expect(map._canvasContainer.classList.contains('maplibregl-track-pointer')).toBeTruthy();
1066
+ expect(popup.getElement().classList).toContain('maplibregl-popup-track-pointer');
1067
+ expect(map._canvasContainer.classList).toContain('maplibregl-track-pointer');
1068
1068
 
1069
1069
  popup.remove();
1070
1070
  });
@@ -1,8 +1,13 @@
1
1
  import {describe, test, expect, vi} from 'vitest';
2
2
  import {Event, Evented} from './evented.ts';
3
3
 
4
+ class StubbedEvent extends Event<'a' | 'b'> {
5
+ foo?: string;
6
+ foz?: string;
7
+ }
8
+
4
9
  // `Evented` is abstract, so use a minimal concrete subclass to exercise its behavior.
5
- class StubbedEvented extends Evented {}
10
+ class StubbedEvented extends Evented<Record<StubbedEvent['type'], StubbedEvent>> {}
6
11
 
7
12
  describe('Evented', () => {
8
13
 
@@ -10,8 +15,8 @@ describe('Evented', () => {
10
15
  const evented = new StubbedEvented();
11
16
  const listener = vi.fn();
12
17
  evented.on('a', listener);
13
- evented.fire(new Event('a'));
14
- evented.fire(new Event('a'));
18
+ evented.fire(new StubbedEvent('a'));
19
+ evented.fire(new StubbedEvent('a'));
15
20
  expect(listener).toHaveBeenCalledTimes(2);
16
21
  });
17
22
 
@@ -19,8 +24,8 @@ describe('Evented', () => {
19
24
  const evented = new StubbedEvented();
20
25
  const listener = vi.fn();
21
26
  evented.once('a', listener);
22
- evented.fire(new Event('a'));
23
- evented.fire(new Event('a'));
27
+ evented.fire(new StubbedEvent('a'));
28
+ evented.fire(new StubbedEvent('a'));
24
29
  expect(listener).toHaveBeenCalledTimes(1);
25
30
  expect(evented.listens('a')).toBeFalsy();
26
31
  });
@@ -29,17 +34,17 @@ describe('Evented', () => {
29
34
  const evented = new StubbedEvented();
30
35
  const listener = vi.fn();
31
36
  const subscription = evented.on('a', listener);
32
- evented.fire(new Event('a'));
37
+ evented.fire(new StubbedEvent('a'));
33
38
  subscription.unsubscribe();
34
- evented.fire(new Event('a'));
39
+ evented.fire(new StubbedEvent('a'));
35
40
  expect(listener).toHaveBeenCalledTimes(1);
36
41
  });
37
42
 
38
43
  test('returns a promise when no listener is provided to "once" method', async () => {
39
44
  const evented = new StubbedEvented();
40
45
  const promise = evented.once('a');
41
- evented.fire(new Event('a'));
42
- evented.fire(new Event('a'));
46
+ evented.fire(new StubbedEvent('a'));
47
+ evented.fire(new StubbedEvent('a'));
43
48
  await promise;
44
49
  expect(evented.listens('a')).toBeFalsy();
45
50
 
@@ -50,7 +55,7 @@ describe('Evented', () => {
50
55
  evented.on('a', (data) => {
51
56
  expect(data.foo).toBe('bar');
52
57
  });
53
- evented.fire(new Event('a', {foo: 'bar'}));
58
+ evented.fire(new StubbedEvent('a', {foo: 'bar'}));
54
59
 
55
60
  });
56
61
 
@@ -59,7 +64,7 @@ describe('Evented', () => {
59
64
  evented.on('a', (data) => {
60
65
  expect(data.target).toBe(evented);
61
66
  });
62
- evented.fire(new Event('a'));
67
+ evented.fire(new StubbedEvent('a'));
63
68
 
64
69
  });
65
70
 
@@ -68,7 +73,7 @@ describe('Evented', () => {
68
73
  evented.on('a', (data) => {
69
74
  expect(data.type).toBe('a');
70
75
  });
71
- evented.fire(new Event('a'));
76
+ evented.fire(new StubbedEvent('a'));
72
77
 
73
78
  });
74
79
 
@@ -77,7 +82,7 @@ describe('Evented', () => {
77
82
  const listener = vi.fn();
78
83
  evented.on('a', listener);
79
84
  evented.off('a', listener);
80
- evented.fire(new Event('a'));
85
+ evented.fire(new StubbedEvent('a'));
81
86
  expect(listener).not.toHaveBeenCalled();
82
87
  });
83
88
 
@@ -86,7 +91,7 @@ describe('Evented', () => {
86
91
  const listener = vi.fn();
87
92
  evented.once('a', listener);
88
93
  evented.off('a', listener);
89
- evented.fire(new Event('a'));
94
+ evented.fire(new StubbedEvent('a'));
90
95
  expect(listener).not.toHaveBeenCalled();
91
96
  });
92
97
 
@@ -95,9 +100,9 @@ describe('Evented', () => {
95
100
  const listener = vi.fn();
96
101
  evented.once('a', () => {
97
102
  listener();
98
- evented.fire(new Event('a'));
103
+ evented.fire(new StubbedEvent('a'));
99
104
  });
100
- evented.fire(new Event('a'));
105
+ evented.fire(new StubbedEvent('a'));
101
106
  expect(listener).toHaveBeenCalledTimes(1);
102
107
  });
103
108
 
@@ -124,7 +129,7 @@ describe('Evented', () => {
124
129
  evented.on('a', () => {
125
130
  evented.on('a', nestedListener);
126
131
  });
127
- evented.fire(new Event('a'));
132
+ evented.fire(new StubbedEvent('a'));
128
133
 
129
134
  expect(nestedListener).not.toHaveBeenCalled();
130
135
  });
@@ -147,7 +152,7 @@ describe('Evented', () => {
147
152
  evented.on('a', listenerA);
148
153
  evented.on('a', listenerB);
149
154
  evented.on('a', listenerA);
150
- evented.fire(new Event('a'));
155
+ evented.fire(new StubbedEvent('a'));
151
156
  expect(listenerA).toHaveBeenCalledTimes(1);
152
157
  expect(order).toEqual(['A', 'B']);
153
158
 
@@ -162,8 +167,8 @@ describe('evented parents', () => {
162
167
  const eventedSink = new StubbedEvented();
163
168
  eventedSource.setEventedParent(eventedSink);
164
169
  eventedSink.on('a', listener);
165
- eventedSource.fire(new Event('a'));
166
- eventedSource.fire(new Event('a'));
170
+ eventedSource.fire(new StubbedEvent('a'));
171
+ eventedSource.fire(new StubbedEvent('a'));
167
172
  expect(listener).toHaveBeenCalledTimes(2);
168
173
  });
169
174
 
@@ -174,7 +179,7 @@ describe('evented parents', () => {
174
179
  eventedSink.on('a', (data) => {
175
180
  expect(data.foo).toBe('bar');
176
181
  });
177
- eventedSource.fire(new Event('a', {foo: 'bar'}));
182
+ eventedSource.fire(new StubbedEvent('a', {foo: 'bar'}));
178
183
 
179
184
  });
180
185
 
@@ -185,7 +190,7 @@ describe('evented parents', () => {
185
190
  eventedSink.on('a', (data) => {
186
191
  expect(data.foz).toBe('baz');
187
192
  });
188
- eventedSource.fire(new Event('a', {foo: 'bar'}));
193
+ eventedSource.fire(new StubbedEvent('a', {foo: 'bar'}));
189
194
 
190
195
  });
191
196
 
@@ -196,7 +201,7 @@ describe('evented parents', () => {
196
201
  eventedSink.on('a', (data) => {
197
202
  expect(data.foz).toBe('baz');
198
203
  });
199
- eventedSource.fire(new Event('a', {foo: 'bar'}));
204
+ eventedSource.fire(new StubbedEvent('a', {foo: 'bar'}));
200
205
 
201
206
  });
202
207
 
@@ -208,7 +213,7 @@ describe('evented parents', () => {
208
213
  eventedSink.on('a', (data) => {
209
214
  expect(data.target).toBe(eventedSource);
210
215
  });
211
- eventedSource.fire(new Event('a'));
216
+ eventedSource.fire(new StubbedEvent('a'));
212
217
 
213
218
  });
214
219
 
@@ -219,7 +224,7 @@ describe('evented parents', () => {
219
224
  eventedSink.on('a', listener);
220
225
  eventedSource.setEventedParent(eventedSink);
221
226
  eventedSource.setEventedParent(null);
222
- eventedSource.fire(new Event('a'));
227
+ eventedSource.fire(new StubbedEvent('a'));
223
228
  expect(listener).not.toHaveBeenCalled();
224
229
  });
225
230
 
@@ -236,11 +241,11 @@ describe('evented parents', () => {
236
241
  const eventedSource = new StubbedEvented();
237
242
  const eventedParent = new StubbedEvented();
238
243
  let i = 0;
239
- eventedSource.setEventedParent(eventedParent, () => i++);
244
+ eventedSource.setEventedParent(eventedParent, () => ({counter: i++}));
240
245
  eventedSource.on('a', () => {});
241
- eventedSource.fire(new Event('a'));
246
+ eventedSource.fire(new StubbedEvent('a'));
242
247
  expect(i).toBe(1);
243
- eventedSource.fire(new Event('a'));
248
+ eventedSource.fire(new StubbedEvent('a'));
244
249
  expect(i).toBe(2);
245
250
 
246
251
  });