maplibre-gl 3.2.2 → 3.3.1

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 (46) hide show
  1. package/LICENSE.txt +1 -1
  2. package/README.md +2 -1
  3. package/build/generate-dist-package.js +7 -2
  4. package/build/generate-struct-arrays.ts +3 -1
  5. package/build/generate-style-code.ts +7 -8
  6. package/build/generate-typings.ts +1 -1
  7. package/dist/LICENSE.txt +116 -0
  8. package/dist/maplibre-gl-csp-worker.js +1 -1
  9. package/dist/maplibre-gl-csp-worker.js.map +1 -1
  10. package/dist/maplibre-gl-csp.js +1 -1
  11. package/dist/maplibre-gl-csp.js.map +1 -1
  12. package/dist/maplibre-gl-dev.js +472 -195
  13. package/dist/maplibre-gl-dev.js.map +1 -1
  14. package/dist/maplibre-gl.d.ts +58 -16
  15. package/dist/maplibre-gl.js +3 -3
  16. package/dist/maplibre-gl.js.map +1 -1
  17. package/dist/package.json +1 -1
  18. package/package.json +24 -23
  19. package/src/data/array_types.g.ts +78 -14
  20. package/src/data/bucket/symbol_attributes.ts +7 -1
  21. package/src/data/bucket/symbol_bucket.ts +4 -1
  22. package/src/render/draw_symbol.ts +8 -9
  23. package/src/render/program.ts +15 -0
  24. package/src/source/vector_tile_source.ts +0 -1
  25. package/src/source/video_source.ts +4 -0
  26. package/src/style/properties.ts +4 -0
  27. package/src/style/style.ts +14 -8
  28. package/src/style/style_layer/background_style_layer_properties.g.ts +1 -6
  29. package/src/style/style_layer/circle_style_layer_properties.g.ts +1 -6
  30. package/src/style/style_layer/fill_extrusion_style_layer_properties.g.ts +1 -6
  31. package/src/style/style_layer/fill_style_layer_properties.g.ts +1 -6
  32. package/src/style/style_layer/heatmap_style_layer_properties.g.ts +1 -6
  33. package/src/style/style_layer/hillshade_style_layer_properties.g.ts +1 -6
  34. package/src/style/style_layer/line_style_layer_properties.g.ts +1 -6
  35. package/src/style/style_layer/raster_style_layer_properties.g.ts +1 -6
  36. package/src/style/style_layer/symbol_style_layer_properties.g.ts +4 -6
  37. package/src/style/style_layer/variable_text_anchor.test.ts +117 -0
  38. package/src/style/style_layer/variable_text_anchor.ts +163 -0
  39. package/src/symbol/placement.ts +52 -40
  40. package/src/symbol/symbol_layout.ts +42 -116
  41. package/src/ui/control/navigation_control.ts +0 -1
  42. package/src/ui/map.test.ts +37 -8
  43. package/src/ui/map.ts +14 -13
  44. package/src/ui/marker.ts +1 -1
  45. package/src/ui/popup.ts +1 -1
  46. package/src/util/throttle.ts +7 -3
@@ -19,7 +19,7 @@ import {SIZE_PACK_FACTOR, MAX_PACKED_SIZE, MAX_GLYPH_ICON_SIZE} from './symbol_s
19
19
  import ONE_EM from './one_em';
20
20
  import type {CanonicalTileID} from '../source/tile_id';
21
21
  import type {Shaping, PositionedIcon, TextJustify} from './shaping';
22
- import type {CollisionBoxArray} from '../data/array_types.g';
22
+ import type {CollisionBoxArray, TextAnchorOffsetArray} from '../data/array_types.g';
23
23
  import type {SymbolFeature} from '../data/bucket/symbol_bucket';
24
24
  import type {StyleImage} from '../style/style_image';
25
25
  import type {StyleGlyph} from '../style/style_glyph';
@@ -31,6 +31,8 @@ import type {PossiblyEvaluatedPropertyValue} from '../style/properties';
31
31
  import Point from '@mapbox/point-geometry';
32
32
  import murmur3 from 'murmurhash-js';
33
33
  import {getIconPadding, SymbolPadding} from '../style/style_layer/symbol_style_layer';
34
+ import {VariableAnchorOffsetCollection} from '@maplibre/maplibre-gl-style-spec';
35
+ import {getTextVariableAnchorOffset, evaluateVariableOffset, INVALID_TEXT_OFFSET, TextAnchor, TextAnchorEnum} from '../style/style_layer/variable_text_anchor';
34
36
 
35
37
  // The symbol layout process needs `text-size` evaluated at up to five different zoom levels, and
36
38
  // `icon-size` at up to three:
@@ -59,98 +61,6 @@ type ShapedTextOrientations = {
59
61
  horizontal: Record<TextJustify, Shaping>;
60
62
  };
61
63
 
62
- export type TextAnchor = 'center' | 'left' | 'right' | 'top' | 'bottom' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
63
-
64
- // The radial offset is to the edge of the text box
65
- // In the horizontal direction, the edge of the text box is where glyphs start
66
- // But in the vertical direction, the glyphs appear to "start" at the baseline
67
- // We don't actually load baseline data, but we assume an offset of ONE_EM - 17
68
- // (see "yOffset" in shaping.js)
69
- const baselineOffset = 7;
70
- const INVALID_TEXT_OFFSET = Number.POSITIVE_INFINITY;
71
-
72
- export function evaluateVariableOffset(anchor: TextAnchor, offset: [number, number]) {
73
-
74
- function fromRadialOffset(anchor: TextAnchor, radialOffset: number) {
75
- let x = 0, y = 0;
76
- if (radialOffset < 0) radialOffset = 0; // Ignore negative offset.
77
- // solve for r where r^2 + r^2 = radialOffset^2
78
- const hypotenuse = radialOffset / Math.sqrt(2);
79
- switch (anchor) {
80
- case 'top-right':
81
- case 'top-left':
82
- y = hypotenuse - baselineOffset;
83
- break;
84
- case 'bottom-right':
85
- case 'bottom-left':
86
- y = -hypotenuse + baselineOffset;
87
- break;
88
- case 'bottom':
89
- y = -radialOffset + baselineOffset;
90
- break;
91
- case 'top':
92
- y = radialOffset - baselineOffset;
93
- break;
94
- }
95
-
96
- switch (anchor) {
97
- case 'top-right':
98
- case 'bottom-right':
99
- x = -hypotenuse;
100
- break;
101
- case 'top-left':
102
- case 'bottom-left':
103
- x = hypotenuse;
104
- break;
105
- case 'left':
106
- x = radialOffset;
107
- break;
108
- case 'right':
109
- x = -radialOffset;
110
- break;
111
- }
112
-
113
- return [x, y];
114
- }
115
-
116
- function fromTextOffset(anchor: TextAnchor, offsetX: number, offsetY: number) {
117
- let x = 0, y = 0;
118
- // Use absolute offset values.
119
- offsetX = Math.abs(offsetX);
120
- offsetY = Math.abs(offsetY);
121
-
122
- switch (anchor) {
123
- case 'top-right':
124
- case 'top-left':
125
- case 'top':
126
- y = offsetY - baselineOffset;
127
- break;
128
- case 'bottom-right':
129
- case 'bottom-left':
130
- case 'bottom':
131
- y = -offsetY + baselineOffset;
132
- break;
133
- }
134
-
135
- switch (anchor) {
136
- case 'top-right':
137
- case 'bottom-right':
138
- case 'right':
139
- x = -offsetX;
140
- break;
141
- case 'top-left':
142
- case 'bottom-left':
143
- case 'left':
144
- x = offsetX;
145
- break;
146
- }
147
-
148
- return [x, y];
149
- }
150
-
151
- return (offset[1] !== INVALID_TEXT_OFFSET) ? fromTextOffset(anchor, offset[0], offset[1]) : fromRadialOffset(anchor, offset[0]);
152
- }
153
-
154
64
  export function performSymbolLayout(args: {
155
65
  bucket: SymbolBucket;
156
66
  glyphMap: {
@@ -175,8 +85,9 @@ export function performSymbolLayout(args: {
175
85
  args.bucket.compareText = {};
176
86
  args.bucket.iconsNeedLinear = false;
177
87
 
178
- const layout = args.bucket.layers[0].layout;
179
- const unevaluatedLayoutValues = args.bucket.layers[0]._unevaluatedLayout._values;
88
+ const layer = args.bucket.layers[0];
89
+ const layout = layer.layout;
90
+ const unevaluatedLayoutValues = layer._unevaluatedLayout._values;
180
91
 
181
92
  const sizes: Sizes = {
182
93
  // Filled in below, if *SizeData.kind is 'composite'
@@ -226,16 +137,16 @@ export function performSymbolLayout(args: {
226
137
  const spacingIfAllowed = allowsLetterSpacing(unformattedText) ? spacing : 0;
227
138
 
228
139
  const textAnchor = layout.get('text-anchor').evaluate(feature, {}, args.canonical);
229
- const variableTextAnchor = layout.get('text-variable-anchor');
140
+ const variableAnchorOffset = getTextVariableAnchorOffset(layer, feature, args.canonical);
230
141
 
231
- if (!variableTextAnchor) {
142
+ if (!variableAnchorOffset) {
232
143
  const radialOffset = layout.get('text-radial-offset').evaluate(feature, {}, args.canonical);
233
144
  // Layers with variable anchors use the `text-radial-offset` property and the [x, y] offset vector
234
145
  // is calculated at placement time instead of layout time
235
146
  if (radialOffset) {
236
147
  // The style spec says don't use `text-offset` and `text-radial-offset` together
237
148
  // but doesn't actually specify what happens if you use both. We go with the radial offset.
238
- textOffset = evaluateVariableOffset(textAnchor, [radialOffset * ONE_EM, INVALID_TEXT_OFFSET]) as [number, number];
149
+ textOffset = evaluateVariableOffset(textAnchor, [radialOffset * ONE_EM, INVALID_TEXT_OFFSET]);
239
150
  } else {
240
151
  textOffset = (layout.get('text-offset').evaluate(feature, {}, args.canonical).map(t => t * ONE_EM) as [number, number]);
241
152
  }
@@ -261,14 +172,19 @@ export function performSymbolLayout(args: {
261
172
  };
262
173
 
263
174
  // If this layer uses text-variable-anchor, generate shapings for all justification possibilities.
264
- if (!textAlongLine && variableTextAnchor) {
265
- const justifications = textJustify === 'auto' ?
266
- variableTextAnchor.map(a => getAnchorJustification(a)) :
267
- [textJustify];
175
+ if (!textAlongLine && variableAnchorOffset) {
176
+ const justifications = new Set<TextJustify>();
177
+
178
+ if (textJustify === 'auto') {
179
+ for (let i = 0; i < variableAnchorOffset.values.length; i += 2) {
180
+ justifications.add(getAnchorJustification(variableAnchorOffset.values[i] as TextAnchor));
181
+ }
182
+ } else {
183
+ justifications.add(textJustify);
184
+ }
268
185
 
269
186
  let singleLine = false;
270
- for (let i = 0; i < justifications.length; i++) {
271
- const justification: TextJustify = justifications[i];
187
+ for (const justification of justifications) {
272
188
  if (shapedTextOrientations.horizontal[justification]) continue;
273
189
  if (singleLine) {
274
190
  // If the shaping for the first justification was only a single line, we
@@ -486,6 +402,22 @@ function addFeature(bucket: SymbolBucket,
486
402
  }
487
403
  }
488
404
 
405
+ function addTextVariableAnchorOffsets(textAnchorOffsets: TextAnchorOffsetArray, variableAnchorOffset: VariableAnchorOffsetCollection): [number, number] {
406
+ const startIndex = textAnchorOffsets.length;
407
+ const values = variableAnchorOffset?.values;
408
+
409
+ if (values?.length > 0) {
410
+ for (let i = 0; i < values.length; i += 2) {
411
+ const anchor = TextAnchorEnum[values[i] as TextAnchor];
412
+ const offset = values[i + 1] as [number, number];
413
+
414
+ textAnchorOffsets.emplaceBack(anchor, offset[0], offset[1]);
415
+ }
416
+ }
417
+
418
+ return [startIndex, textAnchorOffsets.length];
419
+ }
420
+
489
421
  function addTextVertices(bucket: SymbolBucket,
490
422
  anchor: Point,
491
423
  shapedText: Shaping,
@@ -602,15 +534,6 @@ function addSymbol(bucket: SymbolBucket,
602
534
  const placedTextSymbolIndices: {[k: string]: number} = {};
603
535
  let key = murmur3('');
604
536
 
605
- let textOffset0 = 0;
606
- let textOffset1 = 0;
607
- if (layer._unevaluatedLayout.getValue('text-radial-offset') === undefined) {
608
- [textOffset0, textOffset1] = (layer.layout.get('text-offset').evaluate(feature, {}, canonical).map(t => t * ONE_EM) as [number, number]);
609
- } else {
610
- textOffset0 = layer.layout.get('text-radial-offset').evaluate(feature, {}, canonical) * ONE_EM;
611
- textOffset1 = INVALID_TEXT_OFFSET;
612
- }
613
-
614
537
  if (bucket.allowVerticalPlacement && shapedTextOrientations.vertical) {
615
538
  const textRotation = layer.layout.get('text-rotate').evaluate(feature, {}, canonical);
616
539
  const verticalTextRotation = textRotation + 90.0;
@@ -763,6 +686,9 @@ function addSymbol(bucket: SymbolBucket,
763
686
  bucket.addToSortKeyRanges(bucket.symbolInstances.length, feature.sortKey as number);
764
687
  }
765
688
 
689
+ const variableAnchorOffset = getTextVariableAnchorOffset(layer, feature, canonical);
690
+ const [textAnchorOffsetStartIndex, textAnchorOffsetEndIndex] = addTextVariableAnchorOffsets(bucket.textAnchorOffsets, variableAnchorOffset);
691
+
766
692
  bucket.symbolInstances.emplaceBack(
767
693
  anchor.x,
768
694
  anchor.y,
@@ -789,9 +715,9 @@ function addSymbol(bucket: SymbolBucket,
789
715
  useRuntimeCollisionCircles,
790
716
  0,
791
717
  textBoxScale,
792
- textOffset0,
793
- textOffset1,
794
- collisionCircleDiameter);
718
+ collisionCircleDiameter,
719
+ textAnchorOffsetStartIndex,
720
+ textAnchorOffsetEndIndex);
795
721
  }
796
722
 
797
723
  function anchorIsTooClose(bucket: SymbolBucket, text: string, repeatDistance: number, anchor: Point) {
@@ -43,7 +43,6 @@ const defaultOptions: NavigationOptions = {
43
43
  * map.addControl(nav, 'top-left');
44
44
  * ```
45
45
  * @see [Display map navigation controls](https://maplibre.org/maplibre-gl-js/docs/examples/navigation/)
46
- * @see [Add a third party vector tile source](https://maplibre.org/maplibre-gl-js/docs/examples/third-party/)
47
46
  */
48
47
  export class NavigationControl implements IControl {
49
48
  _map: Map;
@@ -6,7 +6,7 @@ import {OverscaledTileID} from '../source/tile_id';
6
6
  import {Event, ErrorEvent} from '../util/evented';
7
7
  import simulate from '../../test/unit/lib/simulate_interaction';
8
8
  import {fixedLngLat, fixedNum} from '../../test/unit/lib/fixed';
9
- import {LayerSpecification, SourceSpecification, StyleSpecification} from '@maplibre/maplibre-gl-style-spec';
9
+ import {GeoJSONSourceSpecification, LayerSpecification, SourceSpecification, StyleSpecification} from '@maplibre/maplibre-gl-style-spec';
10
10
  import {RequestTransformFunction} from '../util/request_manager';
11
11
  import {extend} from '../util/util';
12
12
  import {LngLatBoundsLike} from '../geo/lng_lat_bounds';
@@ -675,6 +675,19 @@ describe('Map', () => {
675
675
  map.addSource('fill', source);
676
676
  });
677
677
 
678
+ test('a layer can be added with an embedded source specification', () => {
679
+ const map = createMap({deleteStyle: true});
680
+ const source: GeoJSONSourceSpecification = {
681
+ type: 'geojson',
682
+ data: {type: 'Point', coordinates: [0, 0]}
683
+ };
684
+ map.addLayer({
685
+ id: 'foo',
686
+ type: 'symbol',
687
+ source
688
+ });
689
+ });
690
+
678
691
  test('returns the style with added source and layer', done => {
679
692
  const style = createStyle();
680
693
  const map = createMap({style});
@@ -848,21 +861,30 @@ describe('Map', () => {
848
861
 
849
862
  const map = createMap();
850
863
 
851
- const spyA = jest.spyOn(map, '_update');
852
- const spyB = jest.spyOn(map, 'resize');
864
+ const updateSpy = jest.spyOn(map, '_update');
865
+ const resizeSpy = jest.spyOn(map, 'resize');
853
866
 
854
867
  // The initial "observe" event fired by ResizeObserver should be captured/muted
855
868
  // in the map constructor
856
869
 
857
870
  observerCallback();
858
- expect(spyA).not.toHaveBeenCalled();
859
- expect(spyB).not.toHaveBeenCalled();
871
+ expect(updateSpy).not.toHaveBeenCalled();
872
+ expect(resizeSpy).not.toHaveBeenCalled();
873
+
874
+ // The next "observe" event should fire a resize / _update
860
875
 
861
- // Following "observe" events should fire a resize / _update
876
+ observerCallback();
877
+ expect(updateSpy).toHaveBeenCalled();
878
+ expect(resizeSpy).toHaveBeenCalledTimes(1);
862
879
 
880
+ // Additional "observe" events should be throttled
881
+ observerCallback();
882
+ observerCallback();
863
883
  observerCallback();
864
- expect(spyA).toHaveBeenCalled();
865
- expect(spyB).toHaveBeenCalled();
884
+ observerCallback();
885
+ expect(resizeSpy).toHaveBeenCalledTimes(1);
886
+ await new Promise((resolve) => { setTimeout(resolve, 100); });
887
+ expect(resizeSpy).toHaveBeenCalledTimes(2);
866
888
  });
867
889
 
868
890
  test('width and height correctly rounded', () => {
@@ -2569,6 +2591,13 @@ describe('Map', () => {
2569
2591
  });
2570
2592
  });
2571
2593
 
2594
+ describe('#getTerrain', () => {
2595
+ test('returns null when not set', () => {
2596
+ const map = createMap();
2597
+ expect(map.getTerrain()).toBeNull();
2598
+ });
2599
+ });
2600
+
2572
2601
  describe('#setCooperativeGestures', () => {
2573
2602
  test('returns self', () => {
2574
2603
  const map = createMap();
package/src/ui/map.ts CHANGED
@@ -25,6 +25,7 @@ import {RGBAImage} from '../util/image';
25
25
  import {Event, ErrorEvent, Listener} from '../util/evented';
26
26
  import {MapEventType, MapLayerEventType, MapMouseEvent, MapSourceDataEvent, MapStyleDataEvent} from './events';
27
27
  import {TaskQueue} from '../util/task_queue';
28
+ import {throttle} from '../util/throttle';
28
29
  import {webpSupported} from '../util/webp_supported';
29
30
  import {PerformanceMarkers, PerformanceUtils} from '../util/performance';
30
31
  import {Source, SourceClass} from '../source/source';
@@ -33,9 +34,8 @@ import {StyleLayer} from '../style/style_layer';
33
34
  import type {RequestTransformFunction} from '../util/request_manager';
34
35
  import type {LngLatLike} from '../geo/lng_lat';
35
36
  import type {LngLatBoundsLike} from '../geo/lng_lat_bounds';
36
- import type {FeatureIdentifier, StyleOptions, StyleSetterOptions} from '../style/style';
37
+ import type {AddLayerObject, FeatureIdentifier, StyleOptions, StyleSetterOptions} from '../style/style';
37
38
  import type {MapDataEvent} from './events';
38
- import type {CustomLayerInterface} from '../style/style_layer/custom_style_layer';
39
39
  import type {StyleImage, StyleImageInterface, StyleImageMetadata} from '../style/style_image';
40
40
  import type {PointLike} from './camera';
41
41
  import type {ScrollZoomHandler} from './handler/scroll_zoom';
@@ -51,7 +51,6 @@ import {defaultLocale} from './default_locale';
51
51
  import type {TaskID} from '../util/task_queue';
52
52
  import type {Cancelable} from '../types/cancelable';
53
53
  import type {
54
- LayerSpecification,
55
54
  FilterSpecification,
56
55
  StyleSpecification,
57
56
  LightSpecification,
@@ -640,15 +639,17 @@ export class Map extends Camera {
640
639
  if (typeof window !== 'undefined') {
641
640
  addEventListener('online', this._onWindowOnline, false);
642
641
  let initialResizeEventCaptured = false;
642
+ const throttledResizeCallback = throttle((entries: ResizeObserverEntry[]) => {
643
+ if (this._trackResize && !this._removed) {
644
+ this.resize(entries)._update();
645
+ }
646
+ }, 50);
643
647
  this._resizeObserver = new ResizeObserver((entries) => {
644
648
  if (!initialResizeEventCaptured) {
645
649
  initialResizeEventCaptured = true;
646
650
  return;
647
651
  }
648
-
649
- if (this._trackResize) {
650
- this.resize(entries)._update();
651
- }
652
+ throttledResizeCallback(entries);
652
653
  });
653
654
  this._resizeObserver.observe(this._container);
654
655
  }
@@ -1962,7 +1963,7 @@ export class Map extends Camera {
1962
1963
  * map.setTerrain({ source: 'terrain' });
1963
1964
  * ```
1964
1965
  */
1965
- setTerrain(options: TerrainSpecification): this {
1966
+ setTerrain(options: TerrainSpecification | null): this {
1966
1967
  this.style._checkLoaded();
1967
1968
 
1968
1969
  // clear event handlers
@@ -2017,8 +2018,8 @@ export class Map extends Camera {
2017
2018
  * map.getTerrain(); // { source: 'terrain' };
2018
2019
  * ```
2019
2020
  */
2020
- getTerrain(): TerrainSpecification {
2021
- return this.terrain && this.terrain.options;
2021
+ getTerrain(): TerrainSpecification | null {
2022
+ return this.terrain?.options ?? null;
2022
2023
  }
2023
2024
 
2024
2025
  /**
@@ -2346,7 +2347,7 @@ export class Map extends Camera {
2346
2347
  *
2347
2348
  * @param layer - The layer to add,
2348
2349
  * conforming to either the MapLibre Style Specification's [layer definition](https://maplibre.org/maplibre-style-spec/layers) or,
2349
- * less commonly, the {@link CustomLayerInterface} specification.
2350
+ * less commonly, the {@link CustomLayerInterface} specification. Can also be a layer definition with an embedded source definition.
2350
2351
  * The MapLibre Style Specification's layer definition is appropriate for most layers.
2351
2352
  *
2352
2353
  * @param beforeId - The ID of an existing layer to insert the new layer before,
@@ -2418,7 +2419,7 @@ export class Map extends Camera {
2418
2419
  * @see [Add a vector tile source](https://maplibre.org/maplibre-gl-js/docs/examples/vector-source/)
2419
2420
  * @see [Add a WMS source](https://maplibre.org/maplibre-gl-js/docs/examples/wms/)
2420
2421
  */
2421
- addLayer(layer: (LayerSpecification & {source?: string | SourceSpecification}) | CustomLayerInterface, beforeId?: string) {
2422
+ addLayer(layer: AddLayerObject, beforeId?: string) {
2422
2423
  this._lazyInitEmptyStyle();
2423
2424
  this.style.addLayer(layer, beforeId);
2424
2425
  return this._update(true);
@@ -2475,7 +2476,7 @@ export class Map extends Camera {
2475
2476
  * @see [Filter symbols by toggling a list](https://maplibre.org/maplibre-gl-js/docs/examples/filter-markers/)
2476
2477
  * @see [Filter symbols by text input](https://maplibre.org/maplibre-gl-js/docs/examples/filter-markers-by-input/)
2477
2478
  */
2478
- getLayer(id: string): StyleLayer {
2479
+ getLayer(id: string): StyleLayer | undefined {
2479
2480
  return this.style.getLayer(id);
2480
2481
  }
2481
2482
 
package/src/ui/marker.ts CHANGED
@@ -419,7 +419,7 @@ export class Marker extends Evented {
419
419
  if (!('offset' in popup.options)) {
420
420
  const markerHeight = 41 - (5.8 / 2);
421
421
  const markerRadius = 13.5;
422
- const linearOffset = Math.sqrt(Math.pow(markerRadius, 2) / 2);
422
+ const linearOffset = Math.abs(markerRadius) / Math.SQRT2;
423
423
  popup.options.offset = this._defaultMarker ? {
424
424
  'top': [0, 0],
425
425
  'top-left': [0, 0],
package/src/ui/popup.ts CHANGED
@@ -627,7 +627,7 @@ function normalizeOffset(offset?: Offset | null) {
627
627
 
628
628
  } else if (typeof offset === 'number') {
629
629
  // input specifies a radius from which to calculate offsets at all positions
630
- const cornerOffset = Math.round(Math.sqrt(0.5 * Math.pow(offset, 2)));
630
+ const cornerOffset = Math.round(Math.abs(offset) / Math.SQRT2);
631
631
  return {
632
632
  'center': new Point(0, 0),
633
633
  'top': new Point(0, offset),
@@ -1,21 +1,25 @@
1
1
  /**
2
2
  * Throttle the given function to run at most every `period` milliseconds.
3
3
  */
4
- export function throttle(fn: () => void, time: number): () => ReturnType<typeof setTimeout> {
4
+ export function throttle<T extends (...args: any) => void>(fn: T, time: number): (...args: Parameters<T>) => ReturnType<typeof setTimeout> {
5
5
  let pending = false;
6
6
  let timerId: ReturnType<typeof setTimeout> = null;
7
+ let lastCallContext = null;
8
+ let lastCallArgs: Parameters<T>;
7
9
 
8
10
  const later = () => {
9
11
  timerId = null;
10
12
  if (pending) {
11
- fn();
13
+ fn.apply(lastCallContext, lastCallArgs);
12
14
  timerId = setTimeout(later, time);
13
15
  pending = false;
14
16
  }
15
17
  };
16
18
 
17
- return () => {
19
+ return (...args: Parameters<T>) => {
18
20
  pending = true;
21
+ lastCallContext = this;
22
+ lastCallArgs = args;
19
23
  if (!timerId) {
20
24
  later();
21
25
  }